-
커맨드 패턴 (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에 저장, 네트워크로 전송하는 등 다양한 방법으로 활용할 수 있다.
단점
- 코드가 복잡해지고 클래스가 많아진다.
참고
'디자인 패턴' 카테고리의 다른 글
이터레이터 패턴 (Iterator Pattern) (0) 2022.08.04 인터프리터 패턴 (Interpreter Pattern) (0) 2022.08.03 책임 연쇄 패턴 (Chain of Responsibility Pattern) (0) 2022.08.02 프록시 패턴 (Proxy Pattern) (0) 2022.07.22 플라이웨이트 패턴 (Flyweight Pattern) (0) 2022.07.22