Notice
suyeonme
[디자인 패턴] 커맨드패턴(Command Pattern) 본문
헤드 퍼스트 디자인 패턴을 읽고 정리한 내용입니다.
1. 커맨드패턴(Command Pattern)이란?
요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다. 이러한 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다.
커맨드 패턴 활용 예시
1. 스케줄러, 스레드 풀, 작업 큐와 같은 작업에 사용할 수 있다.
작업 큐를 예시로 들면, 큐 한쪽 끝은 커맨드를 추가할 수 있고, 다른 쪽 끝에는 커맨드를 처리하는 스레드들이 대기하고 있다. 각 스레드는 우선 execute()를 호출하고 호출이 완료되면 커맨드 객체를 버리고 새로운 커맨드 객체를 가져온다. 작업 큐 클래스는 계산 작업을 하는 객체들과 완전히 분리돠어 있다. 큐에 커맨드 패턴을 구현하는 객체를 넣으면 그 객체를 처리하는 스레드가 생기고 자동으로 execute()가 호출된다.
2. 로그 기록을 남기고 애플리케이션이 다운되었을 때 최근 수행된 작업을 복구할 수 있다.
커맨드 패턴을 사용하여 store()와 load()를 추가해서 이러한 기능을 구현할 수 있다. 로그 기록은 어떤 명령을 실행하면서 디스크에 실행 히스토리를 기록하고, 애플리케이션이 다운되면 커맨드 객체를 다시 로딩하여 execute()를 자동으로 순서대로 실행하는 방식으로 작동한다.
동작 순서
- 클라이언트는 커맨드 객체를 생성한다. 커맨드 객체는 Receiver 객체에 전달할 일련의 행동으로 구성된다.
- 커맨드 객체에는 행동과 리시버(Receiver)의 정보가 들어있다. 커맨드 객체에서 제공하는 메소드는 execute() 하나뿐이다. 이 메소드는 행동을 캡슐화하며 리시버에 있는 특정 행동을 처리한다.
- 클라이언트는 Invoker 객체의 setCommand()를 호출하는데, 이 때 커맨드 객체를 넘겨준다.
- Invoker 객체에서 커맨드 객체의 execute()를 호출한다.
- Receiver 객체에 있는 행동 메소드가 호출된다.
1. 커맨드 객체 생성
// 커맨드 인터페이스
public interface Command {
public void execute();
public void undo();
}
2. 커맨드 클래스의 구현 클래스 생성
// 조명을 켤 때 필요한 커맨드 클래스
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 LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo() {
light.on();
}
}
// 오디오를 켜고 끌 때 필요한 커맨드 클래스
public class StereoOnWithCDCommand implements Command {
Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolumn(1);
}
public void undo() {
stereo.off();
stereo.offCD();
stereo.setVolumn(0);
}
}
3. 커맨드 객체를 사용할 Invoker객체 생성
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();
}
}
3.1 널 객체(Null Object) 생성
Null 객체는 리턴할 객체가 없고 클라이언트가 null을 처리하지 않게 하고싶을 때 활용한다.
public class NoCommand implements Command {
public void execute() {}
}
// 널객체를 사용하지 않는 경우, null 체크를 해줘야한다.
public void onButtonWasPushed(int slot) {
if(onCommans[slot] != null) {
onCommans[slot].execute;
}
}
// 널객체를 사용하여 초기화
Command noCommand = new NoCommand();
for(int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
4. 리모콘 코드 테스트
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
// 장치 생성 (Receiver)
Light livingRoomLight = new Light("Living Room");
GarageDoor garageDoor = new GarageDoor("Garage");
CeilingFan ceilingFan = new CeilingFan("Living Room");
// 조명용 커맨드 객체
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor);
GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor);
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff = new CeilingFanoffCommand(ceilingFan);
// 커맨드 로드
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(0, garageDoorUp, garageDoorDown);
remoteControl.setCommand(0, ceilingFanOn, ceilingFanOff);
// 슬롯 키고 끄기
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.undoButtonWasPushed();
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.undoButtonWasPushed();
}
}
4.1 람다 표현식을 사용하여 코드 간결하게하기
람다 표현식을 쓰면 객체의 인스턴스를 생성하는 것대신 그 자리에 함수 객체를 사용할 수 있어서 모든 구상 커맨드 클래스를 지울 수 있다.
이 방법은 Command 인터페이스에 추상 메소드가 하나뿐일 때만 사용할 수 있다.
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Light("livingRoom");
// 람다 표현식을 사용하지 않은 경우
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightoffCommand(livingRoomLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
// 람다 표현식을 사용한 경우
remoteControl.setCommand(0, () -> livingRoomLightOn.on(), () => livingRoomLightOff.off());
여러 동작을 한번에 처리하기(Macro Command)
1. 매크로 커맨드 객체 생성
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();
}
}
}
2. 매크로에 넣을 일련의 커맨드 생성
Light livingRoomLight = new Light("Living Room");
GarageDoor garageDoor = new GarageDoor("Garage");
CeilingFan ceilingFan = new CeilingFan("Living Room");
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor);
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
3. 매크로 커맨드 객체에 생성한 커맨드 넣기
Command[] on = { livingRoomLightOn, garageDoorUp, ceilingFanOn };
Command[] off = { livingRoomLightOff, garageDoorDown, ceilingFanOff }
MacroCommand onMacro = new MacroCommand(on);
MacroCommand offMacro = new MacroCommand(off);
remoteControl.setCommand(0, onMacro, offMacro)
4. 동작 확인
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
'프로그래밍👩🏻💻 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 최소 지식 원칙(Principle of Least Knowledge) (0) | 2022.07.11 |
---|---|
[디자인 패턴] 어댑터 패턴(Adapter Pattern), 퍼사드 패턴(Facade Pattern) (0) | 2022.07.11 |
[디자인 패턴] 싱글톤 패턴(Singleton Pattern) (0) | 2022.07.04 |
[디자인 패턴] 팩토리 패턴(Factory Pattern) (0) | 2022.06.26 |
[디자인 패턴] 데코레이터 패턴(Decorator Patter) (0) | 2022.06.19 |
Comments