////
Search

6장 - 커맨드 패턴

Created
2022/09/03 11:29
Tags
디자인패턴

커맨드 패턴이란?

커맨드 패턴을 이용하면 작업을 요청하는 쪽과 그 작업을 처리하는 쪽을 분리할 수 있다.
커맨드 패턴을 사용하면 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있습니다.
이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있습니다.
명령으로 객체를 매개변수화 함

NoCmmand 객체

NoCommand 객체는 일종의 널 객체
딱히 리턴할 객체도 없고 클라이언트가 null을 처리하지 않게 하고싶을 때 활용하면 좋음
널 객체는 여러 디자인패턴에서 유용하게 사용됨 널 객체를 일종의 디자인 패턴으로 분류

객체마을 식당에서의 예시

주문서는 주문 내용을 캡슐화 함
주문서는 주문 내용을 요구하는 객체
종업원이 주문서 객체를 계산대나 다른 종업원에게 전달해주는 식으로 여기저기 전달될 수 있는 객체
종업원은 주문서를 받고 orderUp() 메소드를 호출함
주문을 받고 카운터로 가서 orderUp() 메소드를 호출해서 식사 준비를 요청하는 객체
주방장은 식사를 준비하는 데 필요한 정보를 가지고 있음
종업원이 orderUp() 메소드를 호출하면 주방장이 그 주문을 받아서 음식을 만들 때 필요한 메소드를 전부 처리
주방장과 종업원은 완전히 분리되어 있음

커맨드 패턴

1.
클라이언트
a.
클라이언트는 커맨드 객체를 생성
2.
리시버
a.
커맨드 객체에 행동과 리시버의 정보가 같이 들어있음
3.
커맨드
a.
커맨드 객체에서 제공하는 메소도는 excute() 하나뿐
b.
이 메소드는 행동을 캡슐화 해 리시버에 있는 특정 행동을 처리
4.
인보커
a.
클라이언트는 인보커(Invoker) 객체의 setCommand() 메소드를 호출
b.
이때 커맨드 객체를 넘겨줌
c.
커맨드 객체가 쓰이지 전까지 인보커 객체에 보관됨
5.
커맨드
a.
인보커에서 커맨드 객체를 호출하면 작동
6.
리시버
a.
리시버에 있는 행동 메소드가 호출됨

첫 번째 커맨드 객체 만들어보기

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(); } }
Java
복사

요구사항

만능 IoT 리모콘
각 버튼으로 여러 가전제품을 제어할 수 있고
마지막 명령은 undo 할 수 있음

커멘드 패턴 적용

슬롯마다 명령이 할당 됨
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
복사
undo가 추가된 커멘드 인터페이스
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
복사
undo를 구현한 커멘드 구현체
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
복사
undo 기능을 포함한 인보커
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
복사
undo 버튼 테스트

여러 동작을 한 번에 처리하기

버튼 한번으로 여러개의 동작을 한번에 처리하는 방법
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);
Java
복사

정리

커맨드 패턴 활용

커맨드로 컴퓨테이션의 한 부분을 패키지로 묶어서 일급 객체 형태로 전달 가능
클라이언트 애플리케이션에서 커맨드 객체를 생성 한 뒤 오랜 시간이 지나도 그 컴퓨테이션을 호출 할 수 있음
커맨드 패턴을 스케줄러나 스레드 풀, 작업 큐와 같은 다양한 작업에 적용 할 수 있음
작업 큐 클래스는 계산 작업을 하는 객체들과 완전히 분리되어 있음
작업 큐 객체는 작업에 신경쓰지 않고 커맨드 패턴을 구현하는 객체를 실행하기만 하면됨

커맨드 패턴 더 활용하기

시스템이 다운되었을때 다시 커맨드를 호출해 복구할 수 있어야 함
커맨드 패턴에서는 store과 load 메소드를 추가해서 이런 기능을 구현할 수 있음
스프레드시트 애플리케이션의 경우 매번 데이터가 변경될 때마다 디스크에 저장하지 않고, 특정 체크 포인트 이후의 모든 행동을 로그에 기록하는 방식으로 복구 시스템을 구축
더 복잡한 애플리케이션에서는 이런 테크닉을 확장해 일련의 작업에 트랜잭션을 활용해 모든 작업이 완벽하게 처리되거나 아무것도 처리되지 않게 롤백되도록 할 수 있음
커맨드 패턴을 사용하면 요청하는 객체와 수행을하는 객체를 분리할 수 있음
이렇게 분리하는 과정의 중심에는 커맨드 객체가가 있고, 이 객체가 행동이 들어있는 리시버를 캡슐화 함
인보커는 무언가 요청할 때 커멘드 객체의 execute()메소드를 호출하면 됨
커맨드 패턴을 활용해 로그 및 트랜잭션 시스템을 구현할 수 있음