커맨드 패턴
요청을 객체의 형태로 캡슐화하여 요청과 해당 요청 수행을 분리하는 디자인 패턴
- 요구 사항을 객체로 캡슐화할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있다.
- 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며 작업 취소 기능도 지원 가능하다.
Client
- ConcreteCommand를 생성하고 Receiver를 설정한다.
Receiver
- 요구 사항을 수행하기 위해 어떤 일을 처리해야 하는지 알고 있는 객체이다.
- ConcreteCommand에서 Receiver를 가지고 있으며 ConcreteCommand는 명령에 따라 Receiver의 기능을 수행한다.
ConcreteCommand
- 특정 행동과 리시버 사이를 연결해 준다.
- Invoker에서 excute(), undo() 등 호출을 통해 요청이 들어오면 ConcreteCommand에 있는 리시버 객체의 메서드를 호출해 작업을 처리하게 된다.
Invoker
- 명령이 들어 있으며 excute(), undo() 등 메서드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해달라는 요구를 하게 된다.
Command
- 모든 커맨드 객체에서 구현해야 하는 인터페이스이며 리시버에 특정 작업을 처리하라는 인 보커의 지시를 전달한다.
예제
- 집에서 블루투스 스피커와 조명을 켜고 끌 수 있는 리모컨이 있다고 생각해보자.
Receiver
public class BlueToothSpeaker {
private String location;
public BlueToothSpeaker(String location) {
this.location = location;
}
public void on() {
System.out.println(location + "의 블루투스 스피커가 켜졌습니다.");
}
public void connectWithPhone() {
System.out.println("휴대폰과 연결 합니다.");
}
public void playingMusic() {
System.out.println("음악을 킵니다.");
}
public void off() {
System.out.println(location + "의 블루투스 스피커를 꺼졌습니다.");
}
}
public class Light {
private String location;
public Light(String feature) {
this.location = feature;
}
public void on() {
System.out.println(location + "의 전등이 켜졌습니다.");
}
public void off() {
System.out.println(location + "의 전등이 꺼졌습니다.");
}
}
- 조명과 블루투스 스피커 클래스가 Receiver가 된다.
- 즉 요구 사항을 수행하기 위해 처리해야 할 일을 가지고 있다.
Command
public interface Command {
public void execute();
public void undo();
}
- Command Interface를 만들어 명령에 필요한 책임들을 설정한다.
- 명령을 실행하는 excute()와 이전 명령을 다시 실행하는 undo()가 있다.
ConcreteCommand
public class SpeakerOnCommand implements Command {
private BlueToothSpeaker blueToothSpeaker;
public SpeakerOnCommand(BlueToothSpeaker blueToothSpeaker) {
this.blueToothSpeaker = blueToothSpeaker;
}
@Override
public void execute() {
blueToothSpeaker.on();
blueToothSpeaker.connectWithPhone();
blueToothSpeaker.playingMusic();
}
@Override
public void undo() {
blueToothSpeaker.off();
}
}
public class SpeakerOffCommand implements Command {
private BlueToothSpeaker blueToothSpeaker;
public SpeakerOffCommand(BlueToothSpeaker blueToothSpeaker) {
this.blueToothSpeaker = blueToothSpeaker;
}
@Override
public void execute() {
blueToothSpeaker.off();
}
@Override
public void undo() {
blueToothSpeaker.on();
blueToothSpeaker.connectWithPhone();
blueToothSpeaker.playingMusic();
}
}
- ConcreteCommand 중 하나인 SpeackerCommand들이다.
- BlueToothSpeaker를 가지고 있고 명령에 맞게 기능을 작동시킨다.
- onCommand는 excute()에 스피커를 킬 것이고 undo일 때 스피커를 끈다.
- offCommand는 onCommand의 반대이다.
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
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() {
light.on();
}
}
- ConcreteCommand 중 하나인 LightCommand들이다.
- Light를 가지고 있고 명령에 맞게 기능을 수행한다.
public class MacroCommand implements Command {
private Command[] commands;
public MacroCommand(Command... commands) {
this.commands = commands;
}
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
@Override
public void undo() {
for (Command command : commands) {
command.undo();
}
}
}
- 매크로 커맨드도 생성하였다.
- 매크로 커맨드는 클라이언트가 원하는 커맨드들을 설정하면 연속으로 알아서 실행해주는 기능으로 만들었다.
Invoker
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Stack<Command> undoCommands;
public RemoteControl() {
onCommands = new Command[3];
offCommands = new Command[3];
Command noCommand = new NoCommand();
for (int i = 0; i < onCommands.length; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommands = new Stack<>();
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonClick(int slot) {
System.out.println("---------------- ON ----------------");
onCommands[slot].execute();
undoCommands.push(onCommands[slot]);
System.out.println();
}
public void offButtonClick(int slot) {
System.out.println("---------------- OFF ----------------");
offCommands[slot].execute();
undoCommands.push(offCommands[slot]);
System.out.println();
}
public void undoButtonClick() {
try {
undoCommands.pop().undo();
} catch (EmptyStackException ignored) {
}
}
}
- 마지막으로 Invoker인 RemoteControl이다.
- on, offCommand들을 배열로 가지고 있다.
- 각 배열 인덱스에 맞게 명령들을 담은 후 클라이언트는 그 인덱스를 통해 기능을 작동시킨다.
- on, offButtonClick을 통해 slot을 지정하면 그 slot에 맞는 index command를 실행시킬 것이다.
- undoCommand는 스택으로 구현하여 마지막에 누른 버튼부터 undo가 수행 가능하게 하였다.
- noCommand는 아무 기능이 없으며 사용자가 버튼에 기능을 넣기 전에 버튼을 눌러도 아무 작동을 하지 않기 위함으로 초기에는 noCommand로 명령을 초기화한다.
테스트
public class RemoteControlTest {
public static void main(String[] args) {
RemoteControl control = new RemoteControl();
Light roomLight = new Light("안방");
BlueToothSpeaker blueToothSpeaker = new BlueToothSpeaker("거실");
LightOnCommand lightOnCommand = new LightOnCommand(roomLight);
LightOffCommand lightOffCommand = new LightOffCommand(roomLight);
SpeakerOnCommand speakerOnCommand = new SpeakerOnCommand(blueToothSpeaker);
SpeakerOffCommand speakerOffCommand = new SpeakerOffCommand(blueToothSpeaker);
MacroCommand macroOnCommand = new MacroCommand(lightOnCommand, speakerOnCommand);
MacroCommand macroOffCommand = new MacroCommand(lightOffCommand, speakerOffCommand);
control.setCommand(0, lightOnCommand, lightOffCommand);
control.setCommand(1, speakerOnCommand, speakerOffCommand);
control.setCommand(2, macroOnCommand, macroOffCommand);
control.onButtonClick(0);
control.onButtonClick(1);
control.offButtonClick(2);
control.undoButtonClick();
}
}
- 우선 Invoker인 RemoteControl를 생성한다.
- 그리고 각 Receiver인 조명과 스피커를 생성한다.
- 생성한 Receiver들을 통해 각 명령을 수행한다.
- MacroCommand는 조명과 스피커의 명령을 동시에 수행할 것이다.
- 그리고 그렇게 생성한 Command들을 RemoteControl에 slot번호와 함께 세팅해준다.
---------------- ON ----------------
안방의 전등이 켜졌습니다.
---------------- ON ----------------
거실의 블루투스 스피커가 켜졌습니다.
휴대폰과 연결 합니다.
음악을 킵니다.
---------------- OFF ----------------
안방의 전등이 꺼졌습니다.
거실의 블루투스 스피커를 꺼졌습니다.
---------------- UNDO ----------------
안방의 전등이 켜졌습니다.
거실의 블루투스 스피커가 켜졌습니다.
휴대폰과 연결 합니다.
음악을 킵니다.
- onButtonClick(0)을 통해 조명을 켰다.
- onButtonClick(1)을 통해 블루투스를 켰다.
- offButtonClick(2)을 통해 매크로를 실행하여 조명과 블루투스를 껐다.
- undoButtonClick으로 이전에 실행한 매크로 명령의 undo를 실행하여 다시 조명과 블루투스를 켠다.
커맨드 패턴을 통해 클라이언트는 구체적인 구현 사항을 알 필요가 없어졌다.
단지 명령어들을 세팅하기만 하고 버튼을 조작하기만 하면 된다.
'디자인 패턴' 카테고리의 다른 글
퍼사드 패턴(Facade Pattern), 최소 지식 원칙 (0) | 2019.12.16 |
---|---|
어댑터 패턴(Adapter Pattern) (0) | 2019.12.16 |
싱글톤 패턴(Singleton Pattern) (0) | 2019.12.12 |
추상 팩토리 패턴(Abstract Factory Pattern) (0) | 2019.12.12 |
팩토리 메서드 패턴(Factory Method Pattern) (0) | 2019.12.11 |