ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 커맨드 패턴 (Command Pattern)
    디자인 패턴 2022. 8. 2. 23:38

    커맨드 패턴 (Command Pattern)


    요청을 캡슐화하여 호출자(invoker) 와 수신자 (receiver) 를 분리하는 패턴

    • 요청을 하는 쪽과 요청을 처리하는 쪽을 디커플링 시킨다.
    • 요청을 처리하는 방법이 바뀌더라도 호출자 코드는 변경되지 않는다.

     

     

     

    커맨드 패턴 적용 전


    불을 껏다 켰다 할 수 있는 Button 클래스가 있고 불에 해당하는 Light 클래스가 있다고 해보자. 

     

    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;
      }
    }
    
    public class Button {
      private Light light;
    
      public Button(Light light) {
        this.light = light;
      }
    
      public void press() {
        light.off(); //불을 켜야한다면..?
      }
    
      // 외부에서 호출한다고 가정
      public static void main(String[] args) {
        Button button = new Button(new Light());
        button.press();
        button.press();
        button.press();
        button.press();
      }
    }

     

    현재 코드에서 Button 은 불을 끄고 켜도록 press 메소드를 통해 on() 혹은 off() 로 요청한다.

    • Invoker -> Button
    • Receiver -> Light

     

    문제점

    문제점1. 

    버튼을 눌렀을 때 불을 켜야 한다면 press 메소드에서 light.off() 를 light.on() 으로 바꿔줘야 한다.

    public void press() {
      light.on();
    }

     

    문제점2.

    Light 가 아닌 Game 클래스를 만들어서 Button 이 Game 을 시작하고 종료해야 한다면 모든 Light 를 Game 으로 수정해야 한다.

    이는 Invoker 와 Receiver 가 타이트하게 연결되어 있어서 발생하는 문제이다.

     

     

     

    커맨드 패턴 적용 후


    위의 문제를 커맨드 패턴을 사용하여 해결해보자. 

     

    Client 가 사용하기 위한 Command 인터페이스가 필요하다. 

    (상황에 따라 abstract 클래스를 사용해도 된다.)

    // execute 만 필요하다면 Runnable 을 사용해도 된다.
    public interface Command {
      void execute();
      void undo();
    }

     

    이제 각 Command 들은 해당 인터페이스를 구현한다.

    Command 를 구현한 구현체 클래스들은 자신들이 어떤 객체를 사용하고, 어떤 메소드를 실행해야 하는지 구체적으로 알고 있어야 한다.

     

    불을 키는 커맨드

    public class LightOnCommand implements Command{
      Light light;
    
      public LightOnCommand(Light light) {
        this.light = light;
      }
    
      @Override
      public void execute() {
        light.on();
      }
    
      @Override
      public void undo() {
        new LightOffCommand(this.light).execute();
      }
    }

     

    불을 끄는 커맨드

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

     

    Button 은 구체적인 Command 를 알 필요 없이 Command 인터페이스만 사용하면 된다.

    public class Button {
      private Stack<Command> st= new Stack<>();
    
      public void press(Command command) {
        command.execute();
        st.push(command);
      }
      
      public void undo() {
        if(!st.isEmpty()) {
          Command command = st.pop();
          command.undo();
        }
      }
    
      public static void main(String[] args) {
        Button button = new Button();
        button.press(new LightOnCommand(new Light()));
      }
    }
    

     

     

     

    장점과 단점


    장점

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

    단점

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

     

     

     

    참고


     
Designed by Tistory.