ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 커맨드패턴
    기타/북스터디 2022. 12. 25. 08:30

    1. 커맨드패턴(Command Pattern)이란?

    커맨드패턴이란 요청을 캡슐화하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴이다.

    요청 자체를 커맨드에 캡슐화하는 것으로 커맨드 안에서는 reeciver는 누구인지, receiver는 어떠한 일을 해야하는지 등 요청을 수행하기 위한 모든 작업을 내제하고 있는 것이다.

    호출자(invoker)는 캡슐화된 커맨드를 가지고 있어 execute()라는 매서드를 호출만 하면 되는 구조이다. 

    커맨드패턴을 사용함으로서 요청을 객체로 캡슐화해서 객체를 서로 다른 요청에 따라 매개변수화할 수 있다.

    또한, 호출자(invoker)입장에서는 받은 요청이 어떠한 과정으로 처리되는지 전혀 신경을 쓸 필요가 없어진다.

     

    출처: 코딩으로 학습하는 GOF의 디자인 패턴(인프런강의, 백기선님)

     

     

    2. 커맨드패턴이 필요한 상황은 언제일까?

    아래의 코드는 버튼을 누르면 불이 켜지는 상황을 가정한 코드이다.

    public class Button {
    
        private Light light;
        
        public Button(Light light) {
        	this.light = light;
        }
        
        public void press() {
        	light.on();
        }
    }
    
    public class Client {
    
    	public static void main(String[] args) {
        	Button button = new Button(new Light());
            button.press();
        }
    }

    위 코드는 어떠한 상황에서 문제일까?

    1. 버튼의 역할이 불을 키는 것이 아닌 끄는 역할을 해야한다면 Button 클래스의 press메서드 구현을 바꿔야한다.

    2. 수신자(receiver)의 코드, 즉 Light쪽 코드가 바뀌면 호출자(invoker)의 코드가 바뀌어야한다.

    3. 버튼의 역할이 불을 키고 끄는 것이 아닌 커튼을 치거나 걷는 역할을 한다면 호출자(invoker)의 내부구현은 모두 바뀌어야한다.

     

    결국, 문제의 원인은 invoker와 receiver간의 강한 결합이다.

    이 문제를 해결하는 방법으로 커맨드패턴을 사용하는 것이다.

     

     

    3. 커맨드패턴을 적용해보자

    먼저, 세부적인커맨드가 구현해야할 커맨드 인터페이스를 만들어주자.

    (반드시 인터페이스일 필요는 없고 추상클래스로 만들어도 무방하다.)

    public interface Command {
        void execute();
    }

    이렇게 메서드를 호출했을 때에 어떠한 일을 단순히 수행하는 추상메서드만 가진 인터페이스는 java에서 Runnable이라는 인터페이스가 존재하여 execute메서드 하나만 필요한 Command를 사용할 경우에는 Runnable을 이용하는 것도 방법이다.

    https://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html 

     

    Runnable (Java Platform SE 7 )

    The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread. The class must define a method of no arguments called run. This interface is designed to provide a common protocol for objects that wish to e

    docs.oracle.com

     

    이제 불을 켜는 커맨드를 구현해보자.

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

    이렇게 요청을 처리하는 세부적인 커맨드를 하나씩 만들어 주면 된다.

     

    이렇게 커맨드를 구현하면 호출자(invoker)인 Button클래스와 클라이언트 코드는 아래와 같이 된다.

    public class Button {
      
        private Command command;
        
        public Button(Command command) {
            this.command = command;
        }
        
        public void press() {
            command.execute();
        }
    }
    
    
    public class Client {
        
        public static void main(String[] args) {
            Button button = new Button(new LightOnCommand(new Light());
            button.press();
        }
    }

    커맨드패턴을 적용함으로서, 버튼이 해야할 역할이 바뀐다고해서 invoker의 코드를 수정할 필요가 없어졌고 단순히 클라이언트에서 Button생성자 호출코드에서 원하는 커맨드만 파라미터로 넣어주면 된다.

     

     

    3. 커맨드패턴의 장점과 단점은 무엇일까?

    - 장점

    1. 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.

    2. 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다.

    3. 커맨드 객체를 로깅, 큐 방식, 취소기능 등 다양한 방법으로 활용할 수 있다.

     

    - 단점

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

     

     


    <참고 자료>

    1. 코딩으로 학습하는 GoF의 디자인 패턴(인프런강의, 백기선님)

    2. 헤드퍼스트 디자인 패턴 개정판

    댓글

Designed by Tistory.