반응형

 

의도

  • 요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴
  • 요청 자체를 캡슐화하는 것. 이를 통해 요청이 서로 다른 사용자를 매개변수로 만들고, 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원한다.

 

장점

  • 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.
  • 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다.
  • 커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다

 

단점

  • 코드가 복잡하고 클래스가 많아진다.

 

알려진 사용 예

  • 자바
    • 쓰레드가 실행하는 Runnable 를 하나의 Command 라고 볼 수 있다.
  • 스프링
    • 스프링 JDBC의 SimpleJdbcInsert 클래스
    • Insert 쿼리를 만들기 위해서 필요한 모든 방법을 가지고 있는 하나의 커맨드 오브젝트라고 볼 수 있다.

 

활용성

  • 수행할 동작을 객체로 매개변수화하고자 할 때. 콜백(callback) 함수, 즉 어딘가 등록되었다가 나중에 호출되는 함수를 사용해서 이러한 매개변수하를 표현할 수 있다. 명령 패턴은 콜백을 객체지향방식으로 나타낸 것이다.
  • 서로 다른 시간에 요청을 명시하고, 저장하며, 실행하고 싶을 때
  • 실행 취소 기능을 지원하고 싶을 때
  • 시스템이 고장 났을 때 재적용이 가능하도록 변경 과정에 대한 로깅을 지원하고 싶을 때
  • 기본적인 연산의 조합으로 만든 상위 수준 연산을 써서 시스템을 구조화하고 싶을 때

 

결과

  • Command는 연산을 호출하는 객체와 연산 수행 방법을 구현하는 객체를 분리한다.
  • Command는 일급 클래스이다. 다른 객체와 같은 방식으로 조작되고 확장할 수 있다.
  • 명령을 여러 개를 복합해서 복합 명령을 만들 수 있다.
    • 복합체 패턴을 이용해서 여러 명령어를 구성할 수 있다.
  • 새로운 Command 객체를 추가하기 쉽다. 기존 클래스(invoker)를 변경할 필요 없이 단지 새로운 명령어에 대응하는 클래스만 정의하면 된다.

 

협력 방법

  • 사용자는 ConcreteCommand 객체를 생성하고 이를 수신자로 지정한다.
  • Invoker 클래스는 ConcreteCommand 객체를 저장한다.
  • Invoker 클래스는 Command에 정의된 Execute()를 호출하여 요청을 발생시킨다. 명령어가 취소 가능한 것이라면 ConcreteCommand는 이전에 Execute() 호출 전 상태의 취소 처리를 위해 저장한다.
  • ConcreteCommand 객체는 요청을 실제 처리할 객체에 정의된 연산을 호출한다.

 

구조

 

실제 구현 구조

소스코드

//invoker
public class Button {

    private Stack<Command> commands = new Stack<>();

    public void press(Command command) {
        command.execute();
        commands.push(command);
    }

    public void undo() {
        if (!commands.empty()) {
            Command command = commands.pop();
            command.undo();
        }
    }

    //main 메서드는 Button 클래스 밖의 외부 코드라고 가정한다.
    public static void main(String[] args) {
        Button button = new Button();
        button.press(new GameStartCommand(new Game()));
        button.press(new LightOnCommand(new Light()));
        button.undo();
        button.undo();
    }
}
//Command
public interface Command {

    void execute();

    void undo();

}
//receiver
public class Game {

    private boolean isOn;

    public void start() {
        System.out.println("게임 시작");
        this.isOn = true;
    }

    public void end() {
        System.out.println("게임 종료");
        this.isOn = false;
    }

    public boolean isOn() {
        return this.isOn;
    }
}
//receiver
public class Light {

    private boolean isOn;

    public void on() {
        System.out.println("불을 켭니다.");
        this.isOn = true;
    }

    public void off() {
        System.out.println("불을 끕니다.");
        this.isOn = false;
    }

    public boolean isOn() {
        return this.isOn;
    }
}
//ConcreteCommand
public class GameEndCommand implements Command {

    private Game game;

    public GameEndCommand(Game game) {
        this.game = game;
    }

    @Override
    public void execute() {
        game.end();
    }

    @Override
    public void undo() {
        new GameStartCommand(this.game).execute();
    }
}
//ConcreteCommand
public class GameStartCommand implements Command {

    private Game game;

    public GameStartCommand(Game game) {
        this.game = game;
    }

    @Override
    public void execute() {
        game.start();
    }

    @Override
    public void undo() {
        new GameEndCommand(this.game).execute();
    }
}
//ConcreteCommand
public class LightOffCommand implements Command {

    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        new LightOnCommand(this.light).execute();
    }
}
//ConcreteCommand
public class LightOnCommand implements Command {

    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        new LightOffCommand(this.light).execute();
    }
}

 

관련 패턴

  • 여러 명령어가 구성된 MacroCommand를 구현하는 데 복합체 패턴을 사용할 수 있다.
  • 취소를 처리할 때 객체의 상태를 관리하는 데 메멘토 패턴을 사용할 수 있다.
  • 명령어가 처리되어 처리된 이력 목록에 저장되기 전에 명령어를 복사해야 한다면 원형 패턴을 사용할 수 있다.

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

http://www.cs.unc.edu/~stotts/GOF/hires/pat5bfso.htm

코딩으로 학습하는 GoF의 디자인 패턴, 백기선

반응형

+ Recent posts