2021. 10. 14. 17:23ㆍComputer Sciences/Design Patterns
문제
지수는 친구로부터 특별한 선물을 받았다. 그건 리모컨인데 내가 명령을 입력하면 이를 동작 시켜주는 특별한 리모컨이다. 그래서 집에서 사용하는 여러 가전제품을 사용하려고 명령을 등록하려고 하는데 문제가 생겼다. 각각의 가전제품에서 명령을 실행하는 방법이 달랐던 것이다. 전등은 켜고 끌 때 on()
, off()
로 수행되고 선풍기는 high()
, medium()
, low()
, off()
와 같이 단계 별로 바람 세기를 조절할 수 있게 되어있다. 그 외에 TV, 냉장고 등 많은 가전제품이 서로 다르게 구현되어 있다. 이 문제를 어떻게 해결해야 할까?
해결 방안
여러 객체의 행위를 통일하고 싶을 때 우리는 인터페이스를 사용했다. 이번에도 인터페이스를 만들어야 겠다. 그렇다면 어떻게 만들까? 일단 리모컨에서는 이 인터페이스를 준수한 명령을 수행하도록 해야겠다. 그래야 어떤 명령이든 처리할 수 있을 테니까. 그리고 그 명령은 실제 제품이 어떻게 돌아가는지는 알고 있어야 한다. 왜냐하면 나중에 리모컨 버튼을 눌렀을 때 우리가 넣어 준 명령이 수행되려면 어떤 동작이 필요한지 알아야 하기 때문이다. 그렇다면 대충 틀이 잡혔다.
새로 만들 인터페이스 이름을 Command
라고 하자. 그리고 execute()
라는 메서드를 만들어서 모든 명령들이 이를 구현해서 실행되도록 한다. 리모컨은 setCommand(Command)
를 통해서 명령을 입력받고 buttonWasPressed()
가 실행되면 입력받은 명령을 실행하도록 한다. 이젠 간단하게 전등을 켜는 명령부터 만들어보자. 전등은 Light
클래스로 만들고 on()
과 off()
로 동작을 수행한다. 이제 이 동작을 명령으로 만들 LightOnCommand
클래스를 만든다. LightOnCommand
는 생성할 때 동작을 알고 있는 Light
를 입력받으며 execute()
시 light.on()
을 수행하도록 구현한다.
이렇게 만들면 리모컨은 Command
인터페이스를 구현한 명령을 수행하기만 하게 된다. 클라이언트는 실제로 동작할 제품과 그 명령을 만들어서 리모컨에 입력만 해주면 버튼을 누를 때마다 리모컨이 명령을 수행한다. 이제 우린 어떤 제품도 리모컨에 입력해서 동작을 처리할 수 있게 됐다.
이렇게 명령을 인터페이스로 통일시키고 동작을 수행하는 역할과 실행시키는 역할을 분리시켰다. 이를 커맨드 패턴(Command Pattern)이라고 한다. 커맨드 패턴을 이용하면 어떤 명령이라도 기존 코드를 변경하지 않으며 추가할 수 있다. 또한 객체 간 역할을 분리할 수 있다. 인보커(리모컨)은 버튼이 눌렸을 때 입력받은 command
의 execute()
메서드를 수행하기만 한다. 클라이언트는 실제 동작할 객체와 그 동작을 수행할 커맨드에 연결 시켜주고 리모컨에 입력만 해주면 된다. 그러면 버튼을 누를 때 리모컨이 알아서 동작을 처리한다. 클라이언트는 리모컨이 어떻게 돌아가는지 알 필요가 없다.
커맨드 패턴
요구 사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수 있는 패턴이다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업 취소 기능도 지원 가능하다.
Client
: 구현된 커맨드(ConcreteCommand)를 생성하고Reciever
를 지정한다.Receiver
: 실제 동작을 수행하는 객체이다.ConcreteCommand
: 인보커에서 execute() 호출을 통해 요청을 하면ConcreteCommand
객체에서 리시버에 있는 메서드를 호출함으로써 그 작업을 처리한다.Command
: 모든 커맨드 객체에서 구현해야 하는 인터페이스이다. 모든 명령은execute()
로 수행되며, 이 메서드에서는 리시버에 특정 작업을 처리하라는 지시를 전달한다.Invoker
: 필요한 작업을 요청할 때 사용하는 객체이다.setCommand()
의 인자로 실행할 커맨드 객체를 입력받고 나중에 실행 메서드를 통해 해당 커맨드를 실행한다.
아래는 간단하게 불을 켜는 리모컨을 커맨드 패턴으로 작성한 클래스 다이어그램이다.
RemoteControlDriver
: 클라이언트에 해당한다. 리시버와 구현 커맨드를 생성하고 인보커에게setCommand()
를 통해 구현된 커맨드를 전달한다. 그리고 나중에SimpleRemoteControl
(인보커)를 통해buttonWasPressed()
를 호출하면Light
(리시버)의 작업이 수행된다.Light
: 리시버에 해당한다. 불이 실제로 켜지고(on) 꺼질 때(off) 수행할 동작을 알고 있다.LightOnCommand
:Command
인터페이스를 구현한 커맨드 객체이다. 이 커맨드에서execute()
를 수행하면 생성자에서 인자로 받은light
의on()
메서드를 수행하도록 한다.Command
: 모든 커맨드가 구현해야 할 인터페이스이다.SimpleRemoteControl
: 인보커에 해당한다. 먼저 클라이언트로부터 커맨드를 입력받는다(setCommand()
). 그리고 나중에 클라이언트가 동작(buttonWasPressed()
)을 요청할 때setCommand()
로 입력받은slot
의execute()
를 수행한다.
이를 코드로 구현하면 아래와 같다.
public class RemoteControlDriver {
public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}
public class Light {
public void on() {
System.out.println("Light is on");
}
public void off() {
System.out.println("Light is off");
}
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
public interface Command {
void execute();
}
public class SimpleRemoteControl {
Command slot;
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}
Wrap up
클라이언트는 실제 동작을 알고 있는 Light
와 실행 방법이 Command
인터페이스로 표준화된 명령 중 LightOnCommand
를 가진 명령을 만들고 리모컨에 명령을 입력했다. 클라이언트는 버튼을 누르기만 하면 원하는 명령을 처리할 수 있다. 이때 명령은 리모컨이 처리하는데 Command
인터페이스를 구현한 것이라면 어떤 것이든지 처리할 수 있다. 즉 Light
뿐만 아니라 TV, 라디오, 컴퓨터, 가습기 등 Command
객체만 구현하고 커맨드로 등록만 해주면 된다.
'Computer Sciences > Design Patterns' 카테고리의 다른 글
8. Facade Pattern (0) | 2021.10.14 |
---|---|
7. Adapter Pattern (0) | 2021.10.14 |
5. Singleton Pattern (0) | 2021.09.15 |
4. Abstract Factory Method (0) | 2021.09.15 |
3. Factory & Factory Method Pattern (0) | 2021.09.15 |