커맨드 패턴은 요청을 객체로 캡슐화하는 행동 디자인 패턴이다.
요청을 보내는 객체가 실제 작업을 직접 호출하지 않고, 중간에 있는 명령 객체의 execute()를 호출한다.
호출자(Invoker)
↓ execute()
명령(Command)
↓ 실제 메서드 호출
수신자(Receiver)
호출자는 명령이 어떻게 처리되는지 알 필요가 없다. 이로 인해 요청을 보내는 객체와 실제 작업을 수행하는 객체의 결합이 느슨해진다.
| 구성 요소 | 역할 |
|---|---|
Command | 명령의 공통 인터페이스 |
ConcreteCommand | 특정 작업을 실행하는 명령 객체 |
Receiver | 실제 작업을 수행하는 객체 |
Invoker | 명령을 저장하고 실행하는 호출자 |
Client | 객체를 생성하고 서로 연결하는 역할 |
리모컨으로 비유하면 리모컨은 Invoker, 조명 켜기 버튼은 Command, 실제 조명은 Receiver가 된다.
모든 명령은 같은 인터페이스를 구현한다.
public interface Command {
void execute();
}
실제 작업을 수행할 조명 객체를 만든다.
public class Light {
public void on() {
System.out.println("조명을 켭니다.");
}
}
조명 켜기 명령은 Light 객체를 보관하고 있다가 execute()가 호출되면 실제 작업을 요청한다.
public class LightOnCommand implements Command {
private final Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
리모컨은 구체적인 기기를 알지 못하고 등록된 명령만 실행한다.
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
Light light = new Light();
Command lightOn = new LightOnCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();
실행 흐름은 다음과 같다.
리모컨 버튼 클릭
↓
LightOnCommand.execute()
↓
Light.on()
새로운 기기가 추가되어도 Command를 구현한 명령만 만들면 되므로 리모컨 코드는 변경하지 않아도 된다.
명령 객체에 반대 작업을 정의하면 실행 취소 기능도 구현할 수 있다.
public interface Command {
void execute();
void undo();
}
public class LightOnCommand implements Command {
private final Light light;
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
호출자가 마지막으로 실행한 명령을 기억하고 있다가 취소 버튼을 누르면 undo()를 호출하면 된다. 여러 단계의 실행 취소가 필요하다면 실행한 명령을 스택에 저장할 수 있다.
커맨드 패턴은 요청을 객체로 만들어 호출자와 실제 작업을 수행하는 수신자를 분리한다.
Invoker → Command → Receiver
요청을 객체로 다루기 때문에 명령을 교체하거나 저장하고, 여러 명령을 조합하거나 실행을 취소할 수 있다. 다만 클래스 수가 늘어나는 만큼 이러한 기능이 실제로 필요한 상황인지 판단한 뒤 적용해야 한다.