커맨드 패턴

커맨드 패턴

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다.

이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다.

  • 명령을 추상화해서 객체로 사용한다.

  • 클라이언트에서 커맨드 객체 생성

    • 커맨드 객체에는 행동과 리시버(receiver)의 정보가 같이 들어있음

    • 커맨드 객체에서 제공하는 메소드는 excute() 하나뿐

      • 행동을 캡슐화하며 리시버에 있는 특정 행동을 처리한다.

  • 인보커(invoker) 객체의 setCommand() 메서드에 커맨드 객체를 넘겨서 호출

    • 넘긴 커맨드객체를 사용하기 전까지 인보커 객체에서 보관

    • ex: remoteControl.setCommand(0, partyOnMacro, partyOffMacro)

  • 인보커에서 커맨드 객체의 excute() 메서드 호출

    • 리시버에 있는 행동 메서드가 호출됨

키워드

  • 인보커(Invoker): 명령이 들어있으며, execute() 메서드를 호출함으로써 커맨드 객체에게 특정 작업을 수행하도록 요청

    • 작업 요구 담당 (종업원)

    • setCommand

  • 커맨드(Command): 커맨드 객체에서 구현해야 하는 인터페이스,

    • execute: 리시버에 특정 작업을 처리하라는 지시를 전달

    • undo: 작업 취소

  • 리시버(Receiver): 요구사항을 수행할 때 어떤일을 처리해야 하는지 알고 있는 객체

    • 작업 처리 담당 (주방장)

    • action

  • 커맨드 객체(ConcreteCommand): 특정 행동과 리시버를 연결해줌

    • 주문서

    • execute: 리시버에 있는 메서드를 호출해서 요청된 작업을 처리

    • undo: 실행했던 작업을 원복 on <--> off

핵심 정리

  • 커맨드 패턴을 사용하면 요청하는 객체요청을 수행하는 객체를 분리 가능

  • 분리하는 과정에 중심에는 커맨드 객체가 있고, 행동이 들어있는 리시버를 캡슐화함

  • 인보커는 무언가 요청할 때 커맨드 객체의 execute() 메서드를 호출하면 됨

  • 커맨드는 인보커를 매개변수화할 수 있음

    • 실행 중에 동적으로 매개변수화를 설정할 수도 있음

  • execute() 메서드가 마지막으로 호출되기 전의 상태로 되돌리는 작업 취소 메서드를 구현하면 커맨드 패턴으로 작업 취소 기능을 구현 가능

  • 매크로 커맨드는 커맨드를 확장해서 여러 커맨드를 한 번에 호출할 수 있게 해주는 가장 간편한 방법

  • 요청을 스스로 처리하는 '스마트;커맨드 객체를 사용하는 경우도 종종 있음

  • 커맨드 패턴을 활용해서 로그 및 트랜잭션 시스템을 구현할 수도 있음

커맨드 객체 만들기

  • 커맨드 객체는 모두 같은 인터페이스를 구현해야 함

  • 오직 excute() 메서드를 하나만 갖고 있음

public interface Command {
  public void execute();
}
public class LighOnCommand implments Command {
  Light light;

  // 생성자에 커맨드 객체로 제어할 특정 조명의 정보가 전달됨
  // execute() 메서드가 호출되면 light 객체가 바로 그 요청의 리시버가 된다.  
  public LightOnCommand(Light light) {
    this.light = light;
  }
  
  public void execute() {
    light.on();
  }
  
  public void undo() {
    light.off();
  }
}
  • 커맨드 객체를 사용해서 리모콘 만들기

public class SimpleRemoteControl {
  Command slot;
  public SimpleReomteControl() {}
  
  public void setCommand(Command command) {
    slot = command;
  }
  public void buttonWasPressed() {
    slot.execute();
  }
}

NoCommand 객체

  • 커맨드 인터페이스에 따르고 excute() 실행시 아무 동작도 하지 않는 커맨드 객체

  • 일종의 널 객체

    • 딱히 리턴할 객체도 없고 클라이언트가 null을 처리하지 않게 하고 싶을 때 활용

  • 명령이 아직 할당되지 않는 부분에 NoCommand 객체를 넣어서 execute()가 호출되어도 문제가 생기지 않도록

작업 취소 기능 추가하기

public interface Command {
  public void execute();
  public void undeo();
}
  • 마지막으로 실행된 명령을 기록하는 인스턴스 변수를 추가

  • Undo 실행시 기록해 뒀던 커맨드 객체 레퍼런스로 undo() 메서드 호출

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 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();
    }
  }
}

항상 리시버가 필요한지? 커맨드 객체에서 직접 execute() 로직을 구현하면 안되나?

  • 일반적으로는 리시버에 있는 행동을 호출하는 '더미' 커맨드 객체를 만든다.

  • 물론 커맨드 객체에서 대부분의 행동을 처리해도 되지만

  • 인보커와 리시버를 분리하기 어려워짐

  • 리시버로 커맨드를 매개변수화할 수 없어짐

커맨드로 컴퓨티에이션(computation)의 한 부분(리시버와 일련의 행동)을 패키지로 묶어 일급 객체 형태로 전달할 수도 있다.

  • 커맨드 객체를 생성한 뒤 오랜 시간이 지나도 그 컴퓨테이션을 호출 가능

  • 심지어 다른 스레드에서도 호출 가능

  • 이점을 활용해 커맨드 패턴을 활용하여 스케줄러, 스레드 풀, 작업 큐와 같은 다양한 작업에 적용함

Last updated