====== the Definition of Command Pattern ======
커맨드 패턴의 정의는 다음과 같습니다.
커맨드 패턴 - 커맨드 패턴을 이용하면 요구 사항을 객체로 캡슐화할 수 있으며, 매개변수를
써서 여러 가지 다른 요구 사항을 집어 넣을 수도 있습니다. 또한 요청 내역을 큐에 저장하거
나 로그로 기록할 수도 있으며, 작업취소 기능도 지원 가능합니다.
하나씩 살펴볼까요? 커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구사항을 캡슐화한 것입니다.
이렇게 하기 위해서 행동과 리시버를 한 객체에 집어넣고, execute()라는 메소드 하나만, 외부에 공개하는 방법
을 씁니다. 이 메소드 호출에 의해서 리시버에서 일련의 작업이 처리됩니다. 외부에서 볼 때는 어떤 객체가 리시버
역할을 하는지, 그 리시버에서 실제로 어떤 일을 하는지 알 수 없습니다. 그냥 execute() 메소드를 호출하면
요구 사항이 처리된다는 것만 알 수 있을 뿐이죠.
이제 커맨드 패턴의 클래스 다이어그램을 살펴보도록 하겠습니다.
{{keywords>Command Pattern}}
===== Class Diagram =====
{{:study:java:design_pattern:command.jpg|Command Pattern}}
Command Pattern의 예로서 가전제품을 조정 가능한 리모컨 API을 살표보도록 하겠습니다.
이왕이면 작업취소 기능까지 구현해보겠습니다.
===== Command class implements an undo function =====
커맨드에서 작업취소 기능을 지원하려면 execute()메소드하고 비슷한
undo()메소드가 있어야 합니다. execute()메소드에서 했던 것과 정 반대의 작업을 처리하면 되겠죠. 커맨드
클래스에 작업취소 기능을 추가하기 전에 우선 Command interface에 undo()메소드를 추가해야 됩니다.
public interface Command{
public void execute();
public void undo();
}
정말 간단하죠? 이제 Light 커맨드 클래스로 들어가서 undo() 메소드를 구현해 볼까요?
%%LightOnCommand%%부터 시작해 보죠. %%LightOnCommand()%%의 exeucte()메소드가 호출되어서 Light의
on()메소드가 호출되었다고 해 봅시다. 그러면 undo()메소드에서는 그 반대로 off()메소드를 호출해야 될 겁니다.
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on();
}
public void undo(){
light.off();
}
}
정말 식은 죽 먹기군요. 이제 %%LightOffCommand%%를 살펴볼까요? 이 클래스의 undo()메소드에서는 Light의
on()메소드만 호출하면 되겠군요.
public class LightOffCommand implements Command{
Light light;
public LightOffCommand(Light light){
this.light = light;
}
public void execute(){
light.off();
}
public void undo(){
light.on();
}
}
정말 쉽죠? 아직 끝난 건 아닙니다. %%RemoteControl%% 클래스에 사용자가 마지막으로 누른 버튼을 기록하고, undo버튼이
눌렸을 때 필요한 작업을 처리하기 위한 코드를 추가해야 합니다.
===== Invoker Class =====
public class RemoteControlWithUndo{
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControlWithUndo(){
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i=0; i<7; i++){
onCommands[i] = noCommand;
offCommands[i]= noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand)[
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed(){
undoCommand.undo();
}
public String toString(){
//toString 코드
}
}
여기서 %%NoCommand%%는 아무것도 하지 않는 Null 객체((딱히 리턴할 객체는 없지만 클라이언트 쪽에서 null을 처리하지 않아도 되도록 하고 싶을 때 널 객체를 활용하면 좋습니다. 예를 들어, 리모컨의 경우에, 처음 리모컨을 가지고 왔을 때는 아무 명령도 할당되지 않은 상태이므로, exeucte() 메소드가 호출됐을 때 아무 일도 하지 않지만, 빈 자리를 채우기 위한 용도로 %%NoCommand%%라는 객체를 집어넣어 두면 편하겠죠. 널 객체는 여러 디자인 패턴에서 유용하게 쓰입니다. 널 객체를 일종의 디자인 패턴으로 분류하기도 합니다.))입니다.
===== Client Class =====
undo 버튼을 테스트 할 수 있는 테스트용 클래스를 만들어 보겠습니다.
public class RemoteLoader{
public static void main(String[] args){
RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
Light livingRoomLight = new Light("Living Room");
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed(0);
}
}