6장 - 커맨드 패턴
- Created
- 2022년 9월 3일 PM 8:29
커맨드 패턴이란?#

- 커맨드 패턴을 이용하면 작업을 요청하는 쪽과 그 작업을 처리하는 쪽을 분리할 수 있다.
- 커맨드 패턴을 사용하면 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있습니다.
- 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있습니다.
- 명령으로 객체를 매개변수화 함
NoCmmand 객체#
- NoCommand 객체는 일종의 널 객체
- 딱히 리턴할 객체도 없고 클라이언트가 null을 처리하지 않게 하고싶을 때 활용하면 좋음
- 널 객체는 여러 디자인패턴에서 유용하게 사용됨 널 객체를 일종의 디자인 패턴으로 분류
객체마을 식당에서의 예시#
- 주문서는 주문 내용을 캡슐화 함
- 주문서는 주문 내용을 요구하는 객체
- 종업원이 주문서 객체를 계산대나 다른 종업원에게 전달해주는 식으로 여기저기 전달될 수 있는 객체
- 종업원은 주문서를 받고 orderUp() 메소드를 호출함
- 주문을 받고 카운터로 가서 orderUp() 메소드를 호출해서 식사 준비를 요청하는 객체
- 주방장은 식사를 준비하는 데 필요한 정보를 가지고 있음
- 종업원이 orderUp() 메소드를 호출하면 주방장이 그 주문을 받아서 음식을 만들 때 필요한 메소드를 전부 처리
- 주방장과 종업원은 완전히 분리되어 있음
커맨드 패턴#
- 클라이언트
- 클라이언트는 커맨드 객체를 생성
- 리시버
- 커맨드 객체에 행동과 리시버의 정보가 같이 들어있음
- 커맨드
- 커맨드 객체에서 제공하는 메소도는 excute() 하나뿐
- 이 메소드는 행동을 캡슐화 해 리시버에 있는 특정 행동을 처리
- 인보커
- 클라이언트는 인보커(Invoker) 객체의 setCommand() 메소드를 호출
- 이때 커맨드 객체를 넘겨줌
- 커맨드 객체가 쓰이지 전까지 인보커 객체에 보관됨
- 커맨드
- 인보커에서 커맨드 객체를 호출하면 작동
- 리시버
- 리시버에 있는 행동 메소드가 호출됨
첫 번째 커맨드 객체 만들어보기#
java
public interface Command {
public void execute();
}
public class Light {
public 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;
}
public void execute() {
light.on();
}
}
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}
public class RemoteControlTest {
public static void main(String[] args) {
// remote 변수가 Invoker 역할을 함
SimpleRemoteControl remote = new SimpleRemoteControl();
// 리시버인 라이트 객체 생성
Light light = new Light();
// 커맨드 객체에 라이트 객체를 전달
LightOnCommand lightOn = new LightOnCommand(light);
// 실행!
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}요구사항#

- 만능 IoT 리모콘
- 각 버튼으로 여러 가전제품을 제어할 수 있고
- 마지막 명령은 undo 할 수 있음
커멘드 패턴 적용#

- 슬롯마다 명령이 할당 됨
java
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
public RemoteControl() {
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;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ 리모컨 -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.toString();
}
}java
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
}
public class StereoOnWithCDCommand implements Command {
Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}java
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
CeilingFan ceilingFan= new CeilingFan("Living Room");
GarageDoor garageDoor = new GarageDoor("Garage");
Stereo stereo = new Stereo("Living Room");
LightOnCommand livingRoomLightOn =
new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff =
new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn =
new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff =
new LightOffCommand(kitchenLight);
CeilingFanOnCommand ceilingFanOn =
new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff =
new CeilingFanOffCommand(ceilingFan);
GarageDoorUpCommand garageDoorUp =
new GarageDoorUpCommand(garageDoor);
GarageDoorDownCommand garageDoorDown =
new GarageDoorDownCommand(garageDoor);
StereoOnWithCDCommand stereoOnWithCD =
new StereoOnWithCDCommand(stereo);
StereoOffCommand stereoOff =
new StereoOffCommand(stereo);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
remoteControl.setCommand(3, stereoOnWithCD, stereoOff);
System.out.println(remoteControl);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.onButtonWasPushed(3);
remoteControl.offButtonWasPushed(3);
}
}작업 취소 기능 구현#
java
public interface Command {
public void execute();
public void undo();
}java
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo() {
light.on();
}
}java
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControl() {
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;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed() {
undoCommand.undo();
}
}java
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();
}
}여러 동작을 한 번에 처리하기#
버튼 한번으로 여러개의 동작을 한번에 처리하는 방법
java
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
}java
Light light = new Light("Living Room");
TV tv = new TV("Living Room");
Stereo stereo = new Stereo("Living Room");
Hottub hottub = new Hottub();
LightOnCommand lightOn = new LightOnCommand(light);
StereoOnCommand stereoOn = new StereoOnCommand(stereo);
TVOnCommand tvOn = new TVOnCommand(tv);
HottubOnCommand hottubOn = new HottubOnCommand(hottub);
LightOffCommand lightOff = new LightOffCommand(light);
StereoOffCommand stereoOff = new StereoOffCommand(stereo);
TVOffCommand tvOff = new TVOffCommand(tv);
HottubOffCommand hottubOff = new HottubOffCommand(hottub);
Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);
remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
System.out.println(remoteControl);
System.out.println("--- 매크로 On---");
remoteControl.onButtonWasPushed(0);
System.out.println("--- 매크로 Off---");
remoteControl.offButtonWasPushed(0);정리#
커맨드 패턴 활용#
- 커맨드로 컴퓨테이션의 한 부분을 패키지로 묶어서 일급 객체 형태로 전달 가능
- 클라이언트 애플리케이션에서 커맨드 객체를 생성 한 뒤 오랜 시간이 지나도 그 컴퓨테이션을 호출 할 수 있음
- 커맨드 패턴을 스케줄러나 스레드 풀, 작업 큐와 같은 다양한 작업에 적용 할 수 있음
- 작업 큐 클래스는 계산 작업을 하는 객체들과 완전히 분리되어 있음
- 작업 큐 객체는 작업에 신경쓰지 않고 커맨드 패턴을 구현하는 객체를 실행하기만 하면됨
커맨드 패턴 더 활용하기#
- 시스템이 다운되었을때 다시 커맨드를 호출해 복구할 수 있어야 함
- 커맨드 패턴에서는 store과 load 메소드를 추가해서 이런 기능을 구현할 수 있음
- 스프레드시트 애플리케이션의 경우 매번 데이터가 변경될 때마다 디스크에 저장하지 않고, 특정 체크 포인트 이후의 모든 행동을 로그에 기록하는 방식으로 복구 시스템을 구축
- 더 복잡한 애플리케이션에서는 이런 테크닉을 확장해 일련의 작업에 트랜잭션을 활용해 모든 작업이 완벽하게 처리되거나 아무것도 처리되지 않게 롤백되도록 할 수 있음
- 커맨드 패턴을 사용하면 요청하는 객체와 수행을하는 객체를 분리할 수 있음
- 이렇게 분리하는 과정의 중심에는 커맨드 객체가가 있고, 이 객체가 행동이 들어있는 리시버를 캡슐화 함
- 인보커는 무언가 요청할 때 커멘드 객체의
execute()메소드를 호출하면 됨 - 커맨드 패턴을 활용해 로그 및 트랜잭션 시스템을 구현할 수 있음