커맨드 패턴 (command pattern)
요구사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을수 있다.
또한 요청 내역을 큐에 저장하거나 로그로 기록할수 도있으며 작업취소 기능도 지원가능 하다.
커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구사항을 캡슐화한다.
이렇게 하기 위해 행동과 리시버를 한 객체에 집어넣고 메소드 하나만 외부에 공개하는 방법을 사용한다.
커맨드 패턴 클래스 다이어그램
식당을 예로들어보자.
1. 손님이 웨이터에게 주문을 한다.
2. 웨이터가 고객의 주문을 주문서에 적는다.
3. 웨이터는 주문서를 주방에 전달하여 주문을 요청한다.
4. 요리사는 주문서에 적힌 주문대로 음식을 자신의 노하우로 만든다.
손님 == 클라이언트
웨이터 == 인보커 객체
주문서 == 커맨드 객체
주방장 == 리시버 객체
주문을 하는것 == setCommand()
주문을 요청하는것 == execute()
으로 볼수있다.
간단한 커맨드 객체를 이용하여 리모컨 코드를 만들어보자.
public interface Command {
public void execute();
}
다음은 커맨드 인터페이스를 구현한 Concrete Class를 만들어 보자.
public class LightOnCommand implements Command {
Light 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) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}
이제 호텔에 있는 만능 리모컨 같은 여러 기능이 있는 리모컨을 만들어보자.
Command를 처리해줄수 있는 인보커 객체
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand(); //아무 동작도 없는 커멘드 객체 일종의 Null 객체
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];
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
for(int i=0; i<7; i++){
sb.append(onCommands[i].getClass().getName()+" / "+offCommands[i].getClass().getName()+"\n");
}
return sb.toString();
}
}
이외 커멘드 인터페이스를 구현해준 구상클래스들
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
public class StereoOnCommand implements Command {
Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
@Override
public void undo() {
stereo.off();
}
}
public class StereoOffCommand implements Command {
Stereo stereo;
public StereoOffCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.off();
}
@Override
public void undo() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
버튼 테스트.
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Light();
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffcommand(livingRoomLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
}
}
매크로 커맨드 사용법.
응용하면 버튼 하나만으로도 여러 작업을 하게 할수있다.
public class MacroCommand implements Command{
Command[] commands;
public MacroCommand(Command[] commands){
this.commands = commands;
}
public void execute() {
for (int o=0; i<commands.length; i++){
commands[i].execute();
}
}
public void undo() {
for (int o=0; i<commands.length; i++){
commands[i].undo();
}
}
}
아래와 같이 여러 커멘드가 모인 커멘드를 만들어 하나의 버튼 커멘드에 넣어줄수 있다
이렇게 일련의 커멘드들은 생성하여 하나의 매크로 커맨드를 만들어 준다.
Command[] macroOn = {lightOn, stereoOn, tvOn };
Command[] macroOff = {lightOff, stereoOff, tvOff };
Macrocommand commandMacroOn = new MacroCommand(macroOn);
Macrocommand commandMacroOff = new MacroCommand(macroOff);
remoteControl.setCommand(0, commandMacroOn, commandMacroOff);
커맨드 패턴 활용.
1. 요청을 큐에 저장하기.
큐 한 쪽 끝은 커멘드를 추가하고 다른 쪽 끝에는 커맨드를 처리하기 위한 스레드들이 대기.
각 스레드에서는 우서 execute() 메소드를 호출하고 그 호출이 완료되고 나면 커맨드 객체를 보내고 새로운 커맨드
객체를 가져온다.
2. 요청을 로그에 기록하기.
자바에서는 이런 메소드를 객체 직렬화를 통해 구현할 수도 있지만, 직렬화와 관련된 제약 조건때문에 쉽지않을수 있다.
커맨더 패턴을 이용한다면, Command 인터페이스에 store(), load() 라는 메소드를 추가하여 기능을 지원 가능하다.
인보커 클래스가 Command1, Command2, Command3.. 각각 커멘드를 execute() 할때마다. 각 Command 들은 store()를
실행시켜 디스크에 각각의 객체를을 저장한다. 컴퓨터가 다운되어 모든작업이 멈추고 이후 컴퓨터가 복구되었을때,
디스크로 부터 각각의 Command1, Command2 .. 작업완료 하지 못한 객체들을 로딩하고 다시 순서대로 작업을 처리한다.
참고.
Head First Design Patterns.
'JAVA > DesignPattern' 카테고리의 다른 글
디자인패턴 - 퍼사드 패턴 (facade pattern) (3) | 2016.06.13 |
---|---|
디자인패턴 - 어댑터 패턴 (adapter pattern) (7) | 2016.06.13 |
디자인패턴 - 싱글턴 패턴 (singleton pattern) (0) | 2016.05.16 |
디자인패턴 - 팩토리 패턴 (factory pattern) (17) | 2016.05.12 |
디자인패턴 - 데코레이터 패턴 (decorator pattern) (3) | 2016.05.04 |
댓글