리모컨이 있다고 해보자.
이 리모컨으로 TV를 켜보는 것을 구상해보자.
public class TV {
public void turnOn() {
System.out.println("tv가 켜졌습니다.");
}
}
public class RemoteController {
TV tv;
public RemoteController(TV tv) {
this.tv = tv;
}
public void turnOn() {
tv.turnOn();
}
}
public class Main {
public static void main(String[] args) {
TV tv = new TV();
RemoteController controller = new RemoteController(tv);
controller.turnOn();
}
}
그런데 이 리모컨은 만능이라서 TV말고도 전등도 킬 수 있다.
전등을 킬 수 있게도 구상해보자.
public class Light {
public void turnOn() {
System.out.println("전등이 켜졌습니다.");
}
}
public class RemoteController {
TV tv;
Light light;
String mode;
String[] modes = {"tv", "light"};
public RemoteController(TV tv, Light light) {
this.tv = tv;
this.light = light;
}
public void setMode(int idx) {
mode = modes[idx];
}
public void turnOn() {
switch (mode) {
case "tv":
tv.turnOn();
break;
case "light":
light.turnOn();
break;
}
}
}
public class Main {
public static void main(String[] args) {
TV tv = new TV();
Light light = new Light();
RemoteController controller = new RemoteController(tv, light);
controller.setMode(0);
controller.turnOn();
controller.setMode(1);
controller.turnOn();
}
}
만능 리모컨이 킬 수 있는 물건(객체)가 늘어날 때마다 코드가 변경이 일어난다.
⇒ OCP위배가 된다는 말이다.
이를 커맨드 패턴을 이용해 해결해보자.
커맨드 패턴이란 다음과 같다.
요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다. 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다.
커맨드 패턴은 3가지로 구성이 되어 있다.
이렇게 딱딱하게 말하면 어렵다. 음식점에 비유를 해보자.
우리가 레스토랑에서 메뉴판을 보고 종업원에게 “스테이크 1개, 와인 1개 주세요.” 라고 주문을 한다.
그러면 종업원은 주문서에 해당 주문을 적고 이를 주방장에게 갖다준다.
주방장은 주문서에 적힌 주문을 보고 요리를 시작한다.
이를 보면 어떤 것을 요구하는 객체, 그 요구를 받아들이고 처리하는 객체로 나뉘어진 것을 볼 수 있다.
다시 3가지 구성으로 돌아가보자.
커맨드
위의 예시에서 주문서에 해당한다. 커맨드를 전달하는 것이다.
리보커
위의 예시에서 종업원에 해당한다. 그저 커맨드를 받아 전달하는 위치이다.
리시버
위의 예시에서 주방장에 해당한다. 리보커에게 전달받은 커맨드를 보고 수행할 뿐이다.
public interface Command {
void execute();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
public class TVOnCommand implements Command {
TV tv;
public TVOnCommand(TV tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.turnOn();
}
}
public class RemoteController {
Command command;
public void setCommand(Command com) {
command = com;
}
public void buttonWasPressed() {
command.execute();
}
}
public class Main {
public static void main(String[] args) {
TV tv = new TV();
Light light = new Light();
Command lightOn = new LightOnCommand(light);
Command tvOn = new TVOnCommand(tv);
RemoteController controller = new RemoteController();
controller.setCommand(lightOn);
controller.buttonWasPressed();
controller.setCommand(tvOn);
controller.buttonWasPressed();
}
}
위 그림을 보면 Invoker가 커맨드를 지정해주고, 지정한 커맨드를 Receiver가 실행만 하는 구조로 되어있다.
이렇게 Invoker, Receiver, Command가 캡슐화 되어있어 결합도가 낮아져 유지보수가 용이해진다.
위에서 정의내용에서 작업내용을 취소할 수 도 있게 만들 수 있다고 했는데 한 번 만들어보자.
public interface Command {
void execute();
void undo();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
@Override
public void undo() {
light.turnOff();
}
}
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public void undo() {
light.turnOn();
}
}
public class RemoteController {
Command command;
Command undoCommand;
public RemoteController() {
Command noCommand = new NoCommand();
command = noCommand;
undoCommand = noCommand;
}
public void setCommand(Command com) {
command = com;
}
public void buttonWasPressed() {
command.execute();
undoCommand = command;
}
public void undoButtonWasPressed() {
undoCommand.undo();
}
}
public class Main {
public static void main(String[] args) {
TV tv = new TV();
Light light = new Light();
Command lightOn = new LightOnCommand(light);
Command tvOn = new TVOnCommand(tv);
RemoteController controller = new RemoteController();
controller.setCommand(lightOn);
controller.buttonWasPressed();
controller.setCommand(tvOn);
controller.buttonWasPressed();
controller.undoButtonWasPressed();
}
}
다음과 같이 진행하면 된다. 간단하게 만들었는데 만약 undo버튼이 여러번 눌리도록 하고 싶다면 stack으로 저장하여 진행하면 된다.
참고
https://victorydntmd.tistory.com/295
https://soojong.tistory.com/entry/디자인패턴-커맨드-패턴Command-Pattern
http://egloos.zum.com/iilii/v/5378691
헤드퍼스트 디자인 패턴