Command Pattern
요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다. 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능, 트랜잭션 시스템을 구현할 수 있다.
리모컨 코드
리모컨의 각 슬롯을 클릭했을 때, 명령을 통해 행동을 수행하고 각 슬롯마다 다른 명령을 수행해야 한다.
Command Interface
- 모든 커맨드 객체에서 구현해야 하는 인터페이스이다.
- execute() 메서드를 생성한다.
public interface Command {
public void execute();
}
Command Concrete
- 행동을 수행할 구상 클래스이다.
- 리시버 객체를 생성한다.
- excute 함수가 호출되면 리시버 객체에게 특정 작업을 처리하라는 지시를 전달한다.
public class LightOnCommand implements Command{
Light light;
// 특정 조명 객체를 받아 인스턴스 변수에 저장
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on(); // light 객체를 리시버로 지정
}
}
public class LightOffCommand implements Command{
Light light;
// 특정 조명 객체를 받아 인스턴스 변수에 저장
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off(); // light 객체를 리시버로 지정
}
}
Receiver Class
- 어떤 행위를 처리해야 하는지 저장하여 요구 사항을 수행할 때 어떤 일을 처리해야 하는지 알고 있다.
public class Light {
String location = "";
public Light(String location) {
this.location = location;
}
// 어떤 행위를 처리해야 하는지 알고있는 객체
public void on() {
System.out.println(location + " light is on");
}
public void off() {
System.out.println(location + " light is off");
}
}
Invoker Class
- 커맨드 객체에게 어떤 행위를 해달라고 요청한다.
- 명령어 저장 메서드를 정의하고, execute() 메소드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 한다.
public class RemoteControl {
// on, off 명령어를 저장할 배열
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;
}
}
// 명령어 저장 메서드 - slot 번호에 on,off 커맨드 객체 저장
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------ Remote Control -------\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();
}
}
Test.java
public class RemoteControlTest{
public static void main(String[] args) {
RemoteControl remote = new RemoteControl(); // 인보커 객체 생성
Light livingRoomlight = new Light("Living Room"); // 위치에 맞게 객체 생성 - 리시버 객체 생성
// 커맨드 객체 생성
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
remote.setCommand(0, livingRoomLightOn, livingRoomLightOff); // 커맨드 객체를 인보커에게 전달, 행위를 슬롯에 저장
// remote.setCommand(0, ()->livingRoomLight.on(), ()->livingRoomLight.off());
remote.onButtonWasPressed(0); // 리시버에 있는 행동 메서드 수행
remote.offButtonWasPressed(0);
}
}
- 리모컨(인보커) 슬롯마다 커맨드 객체가 할당된다.
- 사용자가 버튼을 누르면 해당 Command 객체의 excute()메서드를 호출한다.
- execute() 메서드는 리시버에게 특정 작업을 처리하도록 지시한다.
작업 취소 기능 추가
Command Class
- Command 클래스 내에 작업취소 기능을 수행할 메서드를 추가한다.
public interface Command{
public void execute();
public void 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; // light on 이전 상태이므로 off
}
}
Receiver Class
- undo 시에 처리할 행위를 저장한 메소드를 생성한다.
Invoker Class
- Invoker 클래스에서 undo 명령을 저장할 인스턴스를 추가한다.
- Command 객체에게 undo 명령을 요청할 메서드를 생성한다.
public class RemoteControl {
// on, off 명령어를 저장할 배열
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;
}
// 명령어 저장 메서드 - slot 번호에 on,off 커맨드 객체 저장
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]; // 해당 객체를 undoCommand 레퍼런스 변수로 저장
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];// 해당 객체를 undoCommand 레퍼런스 변수로 저장
}
//undo 버튼 클릭할 시
public void undoButtonWasPushed(int slot) {
undoCommand.undo(); // undo 메서드 호출
}
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\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();
}
}
매크로 커맨드
- 커맨드들을 저장해서 일련의 행동을 한번에 처리할 수 있다.
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();
}
}
}
활용
스케쥴러, 스레드 풀, 작업 큐
- computation의 한 부분(Receiver와 일련의 행동)을 패키지로 묶어서 일급 객체 형태로 전달할 수 있어, 오랜 시간이 지나도 computation을 호출할 수 있다.
- 작업 큐
- 커맨드 인터페이스를 구현하는 객체를 큐에 추가한다.
- 컴퓨테이션을 고정된 개수의 스레드로 제한할 수 있다.
- 스레드는 큐에서 커맨드를 하나씩 제거하면서 커맨드의 excute() 메소드를 호출한다. 메소드 실행이 끝나면 다시 큐에서 새로운 커맨드 객체를 가져간다.
로그 기록
- 어떤 명령을 실행하면서 디스크에 실행 히스토리를 기록하고, 애플리케이션이 다운되면 커맨드 객체를 다시 로딩해서 excute() 메소드를 자동으로 순서대로 실행한다.
- 스프레드시트 애플리케이션
- 데이터가 변경될 때마다 체크포인트 이후의 모든 행동을 로그에 기록하는 방식으로 복구시스템을 구축하였다.
'Books > Head First Design Pattern' 카테고리의 다른 글
| [8장] Template Method Pattern (0) | 2024.05.24 |
|---|---|
| [7장] Adapter Pattern과 Facade Pattern (0) | 2024.05.24 |
| [5장] Singleton Pattern (0) | 2024.05.24 |
| [4장] Factory Pattern (0) | 2024.05.24 |
| [3장] Decorator Pattern (0) | 2024.05.24 |