the Definition of Command Pattern

커맨드 패턴의 정의는 다음과 같습니다.

커맨드 패턴 - 커맨드 패턴을 이용하면 요구 사항을 객체로 캡슐화할 수 있으며, 매개변수를
써서 여러 가지 다른 요구 사항을 집어 넣을 수도 있습니다. 또한 요청 내역을 큐에 저장하거
나 로그로 기록할 수도 있으며, 작업취소 기능도 지원 가능합니다.

하나씩 살펴볼까요? 커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구사항을 캡슐화한 것입니다. 이렇게 하기 위해서 행동과 리시버를 한 객체에 집어넣고, execute()라는 메소드 하나만, 외부에 공개하는 방법 을 씁니다. 이 메소드 호출에 의해서 리시버에서 일련의 작업이 처리됩니다. 외부에서 볼 때는 어떤 객체가 리시버 역할을 하는지, 그 리시버에서 실제로 어떤 일을 하는지 알 수 없습니다. 그냥 execute() 메소드를 호출하면 요구 사항이 처리된다는 것만 알 수 있을 뿐이죠.

이제 커맨드 패턴의 클래스 다이어그램을 살펴보도록 하겠습니다.

Class Diagram

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 객체1)입니다.

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);
 
   }
 
}
1)
딱히 리턴할 객체는 없지만 클라이언트 쪽에서 null을 처리하지 않아도 되도록 하고 싶을 때 널 객체를 활용하면 좋습니다. 예를 들어, 리모컨의 경우에, 처음 리모컨을 가지고 왔을 때는 아무 명령도 할당되지 않은 상태이므로, exeucte() 메소드가 호출됐을 때 아무 일도 하지 않지만, 빈 자리를 채우기 위한 용도로 NoCommand라는 객체를 집어넣어 두면 편하겠죠. 널 객체는 여러 디자인 패턴에서 유용하게 쓰입니다. 널 객체를 일종의 디자인 패턴으로 분류하기도 합니다.

QR Code
QR Code study:java:design_pattern:command (generated for current page)