Command Pattern
Command 디자인 패턴의 필요성과 그 구조에 대해 알아봅니다.읽는데 6분 정도 걸려요.필요성
개발하다보면, 특정 버튼을 누르면 이벤트가 발생하도록 처리하는 패턴을 구현해야 하는 경우가 생길 것이다.
예로들어 만능 리모컨을 만든다고 가정해보자.
버튼 1 - ON을 누르면 tv가 켜지고 OFF를 누르면 꺼지고,
public class TV {
private boolean isOn;
private int channel;
public TV() {
this.isOn = false;
this.channel = 1;
}
public void on() {
this.isOn = true
}
public void off() {
this.isOn = false;
}
public void changeChannel(int channel) {
this.channel = channel;
}
}
버튼 2는 전등, 3은 선풍기 등등...
이런 경우에 보통의 경우 if - else
로 처리하는 경우가 많을 것이다.
if (버튼 1 - ON) {
tv.on();
} else if (버튼 2 - OFF) {
tv.off();
}
하지만 이런 경우에는 새로운 기능을 추가하거나, 기존에 있는 기능을 수정하려면 그 유지보수가 매우 불편하다.
따라서 이를 해결하기 위한 디자인 패턴을 소개한다.
Command Pattern
Command Pattern의 기본적인 아이디어는 모든 기능 요청들을 하나의 명령어(Command)
로서 관리하여 은닉화 하는데에 있다.
바로 코드를 보며 이해해보자.
public interface Command {
public void execute();
}
public class TVOnCommand implements Command {
TV tv;
public TVOnCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.on();
}
}
모든 명령어는 무조건 execute()
로 실행하고, 각 명령어의 실행 방법은 그 명령어가 어떤 장치를 조작하는지에 따라 다르게 구현한다.
따라서 이렇게 구현한다면 TV를 켜든 끄든 전등을 제어하든 모든 명령은 execute()
를 호출하면 되는 것이다.
public class RemoteController {
Command[] onCommands;
Command[] offCommands;
public RemoteController() {
onCommands = new Command[7];
offCommands = new Command[7];
Command empty = new Command();
for (int i=0; i<onCommands.length; i++) {
onCommands[i] = empty;
offCommands[i] = empty;
}
}
public void setCommand(int slot, Command onCom, Command offCom) {
onCommands[slot] = onCom;
offCommands[slot] = offCom;
}
public void onBtnPushed(int slot) {
onCommands[slot].execute();
}
public void offBtnPushed(int slot) {
offCommands[slot].execute();
}
}
그리고 만능 리모컨을 이렇게 구현한다면, 사용자 입장에서는 원하는 기능을 Command interface
로 구현하고, 원하는 slot에 setCommand()
를 이용해 넣어주기만 하면 된다.
추가 기능
명령어 인터페이스를 이용해 하나로 구현하면 다른 기능을 추가하고 싶을 때도 용이하다.
예로 들어 undo 기능을 구현해보자.
public interface Command {
public void execute();
+ public void undo();
}
그럼 각 명령어에서 수행할 undo 기능을 추가만 하면 된다.
public class TVOnCommand implements Command {
TV tv;
public TVOnCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.on();
}
+ public void undo() {
+ tv.off(); // on의 반대는 off
+ }
}
그렇다면 컨드롤러의 구현은 이렇게만 수정되면 undo 기능을 쉽게 구현할 수 있게된다!
public class RemoteController {
Command[] onCommands;
Command[] offCommands;
+ Command undoCommand;
...
public void onBtnPushed(int slot) {
onCommands[slot].execute();
+ undoCommand = onCommands[slot];
}
public void offBtnPushed(int slot) {
offCommands[slot].execute();
+ undoCommand = offCommands[slot];
}
+ public void undoBtnPushed() {
+ undoCommand.undo();
+ }
}
undo 뿐만 아니라, 매크로 기능과 같은 서드파티 기능도 쉽게 구현할 수 있을 것이다.
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
public void execute() {
for (int i=0; i<commands.length; i++) {
commands[i].execute();
}
}
public void undo() {
for (int i=commands.length-1; i>=0 i--) {
commands[i].undo();
}
}
}
IRL
In Real Life에서는 Command Pattern이 어떻게 쓰이고 있을까?
-
명령어 queuing
Command queue를 만들어enqueue
,dequeue
를 구현하여, 각각의 명령어를 순차처리, 스레드에 할당하는 방식으로 사용하는 경우가 있다. -
logging
Command에store
,load
따위의 메서드를 추가하여 명령어가 실행되면 disk에 로깅 및 불러올 수 있도록 기능을 추가하여 에러 및 데이터 분석을 용이하게 하는 경우도 있다.
결론
클라이언트 별로 다른 요청을 처리하는 경우나, 요청 queue, 로깅이 필요한 경우에는
명령어, 요청을 은닉화(encapsulates)
하는 Command Pattern을 활용해보자.