실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있고, 기능 수정과 확장에 있어서 유연한 클래스를 설계하는 패턴이다.
Command
Invoker
Receiver
ConcreteCommand
Client
만능 리모컨
리모컨 버튼이 눌렸을 때 호출되는 코드와 실제로 일을 처리하는 코드를 분리시키는 것이 필요함
➜ 요청과 실행을 분리
➜ 어떤 것을 요구하는 객체 Invoker
와 그 요구를 받아들이고 처리하는 객체 Receiver
를 분리
Invoker의 요구 사항을 Receiver와 연결시킴으로써 일련의 행동을 캡슐화 한 것 Command
➜ 모든 일을 처리해주는 메서드를 만들어서 캡슐화 execute()
[1] Client에서 Receiver 생성
[2] Client에서 ConcreteCommand 생성
[3] 리모컨에 있는 버튼에 Command 지정
[4] 리모컨에 있는 버튼 클릭
[5] 해당 Command를 실행 (Command에 있는 내용은 Receiver에 있는 action을 실행시킴)
Invoker
Receiver
ConcreteCommand
Command
Client
Invoker
가 되는 것이다.Receiver
(전등, 선풍기, 오디오 등)에서 특정 행동을 하는 메서드가 실행되어 작업을 처리한다.public interface Command {
public void execute();
public void undo(); // 작업 취소 기능
}
ConcreteCommand
// 커맨드 객체이므로 Command 인터페이스를 구현
public class LightOnCommand implements Command {
// Receiver인 Light에 대한 reference
Light light;
// 생성자에서 Receiver에 대한 reference, 이 커맨드 객체로 제어(on, off)할 전등 종류에 대한 정보 전달
public LightOnCommand(Light light) {
this.light = light;
}
// execute() 메서드가 호출될 때,
// Recevier인 light 객체에 있는 on() 메서드가 호출된다.
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
ConcreteCommand
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo() {
// 불이 꺼져있으면, 다시 켠다.
light.on();
}
}
Invoker
public class simpleRemoteControl {
// 커맨드를 넣을 하나의 슬롯으로 제어한다. 슬롯이 하나밖에 없음
Command slot;
public simpleRemoteControl() {}
// 클라이언트에서 리모컨의 명령을 바꾸고 싶다면,
// 커맨드 객체를 바꿔 끼울 수 있다.
public void setCommand(Command command) {
slot = command;
}
// 버튼이 눌려지면 이 메서드가 호출된다.
// 지금 연결되어 있는 커맨드 객체의 execute() 메서드가 호출된다.
public void buttonWasPressed() {
slot.execute();
}
}
Client
public class RemoteControlTest {
public static void main(String[] args) {
// remote 변수가 Invoker 역할
// 필요한 작업을 요청할 때 사용할 커맨드 객체를 인자로 받을 예정
SimpleRemoteControl remote = new SimpleRemoteControl();
// given: 요청을 받아서 처리할 Receiver인 Light 객체를 만든다.
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
// when: 커맨드 객체를 Invoker(remote 변수)에 전달
remote.setCommand(lightOn);
// then: Invoker인 remote에서 buttonWasPressed 메서드가 실행되면,
// 지금 리모컨에 연결되어 있는 커맨드 객체(버튼에 연결된)인 lightOn 객체의 excute() 메서드를 실행
remote.buttonWasPressed();
}
}
// 실행 결과: Light is On
public class RemoteControlWithUndo {
// 이 리모컨에서는 7개의 on/off 명령을 처리할 수 있으며
// 여러 개의 Command를 호출해야 하므로 배열로 나타낸다.
Command[] onCommands;
Command[] offCommands;
/*
undoCommand가 배열이 아닌 이유?
가장 최근 커맨드 객체 1개를 되돌리기 위해서 배열이 아닌 것이다.
참조를 지속해서 바꿔준다.
전체를 되돌리려면, Stack으로 구현한다.
(Stack에서 pop()메서드로 빼내면 된다.)
*/
Command undoCommand;
public RemoteControlWithUndo() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
// 클라이언트가 다른 버튼을 누르지 않은 상태에서
// UNDO 버튼을 누르더라도 문제가 생기지 않도록 만든다.
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
undoCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
// 클라이언트가 버튼을 누르면, 슬롯에 연결된 커맨드 객체의 execute()메서드를 호출한 후
// 그 객체의 참조를 undoCommand에 바꿔 끼워준다.
// ex) lighton을 호출했을 때 undo버튼을 눌렀을 때 lightoff기능을 실행시킬 수 있도록
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
// 클라이언트가 UNDO 버튼을 누르면,
// undoCommand에 연결된 커맨드 객체의 undo() 메서드를 호출한다.
public void undoButtonWasPushed() {
// undo() 메서드가 실행되면,
// 가장 최근에 했던 작업이 취소된다.
undoCommand.undo();
}
public String toString() {
// toString 코드...
}
}
public class RemoteLoader {
public static void main(String[] args) {
RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
Light livingRoomLight = new Light("Living Room");
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
}
}