ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Command Pattern
    디자인 패턴 2022. 2. 23. 14:26

    해당 책을 통해 공부한 내용을 정리하고 있습니다.


     

    책에서 정의하는 커맨드 패턴의 정의는 다음과 같다.

     

    요청 내역을 객체로 캡슐화하여 클라이언트를 서로 다른 요청 내역에 따라 매개변수화 할 수 있다. 요청을 큐에 저장하거나 로그로 기록할 수도 있고 작업 취소 기능을 지원 할 수도 있다.

     

     이해를 위해 예시를 들어 정의를 풀어보자. 어떤 요청을 처리하는 객체 ‘Receiver’가 있고, 이 객체에 요청을 보내는 ‘Requester’가 있다고 생각해보자. ‘Requester’는 ‘Receiver’에 처리하기 위한 명령(Command)를 요청(Request) 할 것 이다. 이 명령(Command)들을 따로 객체로 만들어서 캡슐화하는 것이 커맨드 패턴이라고 한다.

     

    phillips hue dimmer

     

     실생활에서 볼 수 있는걸로 예시를 들어보자. 전구를 제어 할 수 있는 위의 이미지처럼 생긴 스위치가 있다고 생각해보자. 각각의 버튼의 기능은 맨 위에서부터, 전구 전원 키기, 전구 밝게하기, 전구 어둡게하기, 전구 끄기이다. 각각의 버튼에 전구들을 직접 불러서 작업을 처리하는 방식으로 하드 코딩 할 수도 있겠지만, 그렇게 한다면 나중에 유지보수 할 때 마다 그 코드를 열어서 뜯어고쳐야 할테니 별로 좋은 방법은 아닐것이다. 이럴 때 사용하기 좋은게 Command 패턴이다. 각각의 버튼에 Command 객체를 붙이고, 누를때마다 그 커맨드 객체가 사용되도록 하면 된다. 이렇게 커맨드 객체가 붙여져 있는 리모컨같은 객체를 ‘Invoker’라고 한다.

     

     


    UML과 구현

     

    from - wikipedia

     커맨드 패턴의 UML은 위와 같다. UML만 보면 어떻게 작동하는지 잘 감이 안오니 한번 코드로 보도록 하자.

    // Receiver, 전구
    public class Light {
        public void on() {
            System.out.println("Light is On");
        };
        public void off() {
            System.out.println("Light is Off");
        };
        public void dim() {
            System.out.println("dim");
        }
        public void lightUp() {
            System.out.println("Light up");
        }
    }
    

    위와 같이 조절해야 하는 전구(Receiver)가 있다고 하자. 전구는 키는 동작과 끄는 동작, 그리고 밝게하기, 어둡게하기 기능이 있다. 그리고 이에 해당하는 명령(Command)를 캡슐화해보자.

    우선 인터페이스부터 구현하자.

    public interface Command {
        public void execute();
    }
    
    

    다음으로 리시버 각각의 명령에 맞는 커맨드 객체를 만들자.

    // 전구를 키는 명령
    public class LightOnCommand implements Command{
        Light light;
    
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.on();
        }
    }
    
    
    // 전구를 끄는 명령
    public class LightOffCommand implements Command{
        Light light;
    
        public LightOffCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.off();
        }
    }
    
    
    // 전구를 어둡게하는 명령
    public class LightDimCommand implements Command{
        Light light;
    
        public LightDimCommand (Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.dim();
        }
    }
    
    // 전구를 밝게하는 명령
    public class LightUpCommand implements Command{
        Light light;
    
        public LightUpCommand (Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.lightUp();
        }
    }
    

    다음으로 인보커 각각의 버튼에 명령을 집어넣자.

    // Invoker
    public class RemoteControl {
        Command on;
        Command off;
        Command dim;
        Command lightUp;
    
    		public void RemoteControl(Command on, Command off, Command dim, Command lightUp) {
    			this.on = on;
    			this.off = off;
    			this.dim = dim;
    			this.lightUp = lightUp;
        }
    
    		public void onButtonWasPushed() {
    				on.execute();
        }
    
        public void offButtonWasPushed() {
            off.execute();
        }
    
        ......
    
    }
    

    이와 같이 구성을 하면 된다. Invoker에서는 Command에 대해 구체적으로 알 필요가 없어서 결합도가 긴밀하지 않아, 유지보수하기에 간편해진다. 또한 여러 프로그램에서 사용하는 Undo 명령을 제공하기에도 간편하다. Undo 명령을 제공하려면 위의 구조에서 명령어를 추가해 다음과 같이 구성하면 된다.

    public interface Command {
        public void execute();
        public void unExecute();
    }
    
    // 전구를 키는 명령
    public class LightOnCommand implements Command{
        Light light;
    
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.on();
        }
    
        @Override
        public void unExecute() {
            light.off();
        }
    }
    

    이런식으로 Undo 또한 간편하게 구현 할 수 있다.

     

     

    Invoker를 인터페이스가 아닌 클래스로 사용하는 이유

     Invoker가 정형화 되어 있지 않기 때문이다. 리모컨을 예시로 들어 설명해보자면, 리모컨은 버튼이 4개인 것도 있고, 버튼이 8개인 것도 있고 엄청 많은 것도 있는데 이를 인터페이스로 묶는 것은 힘든 일이다.

     

     


    Reference

     

    https://www.youtube.com/watch?v=9qA5kw8dcSU 

     

    '디자인 패턴' 카테고리의 다른 글

    Template Method Pattern  (0) 2022.02.26
    Adapter Pattern  (0) 2022.02.25
    Factory Pattern  (0) 2022.02.14
    Decorator Pattern  (0) 2022.02.08
    Observer Pattern  (0) 2022.02.03

    댓글

Designed by Tistory.