Command패턴은 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고도 재사용을 가능하게 할때 유용하다.
실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 Invoker 클래스와 실제 기능을 실행하는 Receiver클래스 사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 Invoker 클래스를 수정없이 그대로 사용할 수 있도록 해준다.
만능버튼 만들기
램프를 켜는 버튼을 만들어보자
public class Lamp {
public void turnOn() {
System.out.println("Lamp On!");
}
}
public class Button {
private Lamp lamp;
public Button(Lamp lamp) {
this.lamp = lamp;
}
public void pressed() {
lamp.turnOn();
}
}
public class Client {
public static void main(String[] args) {
Lamp lamp = new Lamp();
Button lampButton = new Button(lamp);
lampButton.pressed();
}
}
Button클래스의 생성자를 이용해 불을 켤 Lamp객체를 전달한다.
Button클래스의 pressed메소드가 호출되면 생성자를 통해 전달받은 Lamp 객체의 turnOn메소드를 호출해 불을 켠다.
문제점
램프가 켜지는 기능 대신 다른 기능을 실행하려면 어떤 변경 작업을 해야 하는가?
버튼을 누르는 동작에 따라 기능을 실행하게 하려면 어떤 변경 작업을 해야 하는가?
1. 다른 기능의 실행
램프 대신 알람을 시작하게 하려면 Button클래스를 수정해야한다.
public class Alarm {
public void start() {
System.out.println("Rrrrrrr..!");
}
}
public class Button {
private Alarm alarm;
public Button(Alarm alarm) {
this.alarm = alarm;
}
public void pressed() {
alarm.start();
}
}
기능을 변경하려고 Button클래스의 코드를 변경하는것은 OCP에 위배된다. 버튼을 눌렀을때 지정된 특정기능만 고정적으로 수행하도록 만든 처음 디자인은 다른 기능을 추가할때 위 처럼 메소드 전체를 변경해야하므로 OCP를 위배하는 것이다.
2. 누르는 동작에 따라 다른 기능을 실행하는 경우
버튼을 누르는 동작에 따라 다른 기능을 실행하게 하려면 기능이 실행되는 시점에 필요한 프로그램 또는 메소드를 선택할 수 있어야한다.
램프와 알람기능 모두 구현한 코드다.
public class Button {
private Lamp lamp;
private Alarm alarm;
private Mode mode;
enum Mode {LAMP, ALARM};
public Button(Lamp lamp, Alarm alarm) {
this.lamp = lamp;
this.alarm = alarm;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void pressed() {
switch (mode) {
case LAMP:
lamp.turnOn();
break;
case ALARM:
alarm.start();
break;
}
}
}
public class Client {
public static void main(String[] args) {
Lamp lamp = new Lamp();
Alarm alarm = new Alarm();
Button button = new Button(lamp, alarm);
button.setMode(Mode.LAMP);
button.pressed();
button.setMode(Mode.ALARM);
button.pressed();
}
}
이 역시 새로운 기능을 추가할 때 마다 코드를 수정해야해 재사용성이 떨어진다.
해결책
새로운 기능을 추가하거나 변경하더라도 Button클래스를 그대로 사용하려면 Button클래스의 pressed 메소드에서 구체적인 기능을 직접 구현하는 대신 버튼을 눌렀을때 실행될 기능을 Button클래스 외부에서 제공받아 캡슐화해 pressed메소드에서 호출하는 방법을 사용할 수 있다.
다음은 설계다.
Button클래스는 램프 기능을 실행할 때 Lamp클래스의 turnOn메소드나 Alarm클래스의 start메소드를 직접 호출하지 않는다. 대신 미리 약속된 Command인터페이스의 execute메소드를 호출한다.
그리고 LampOnCommand클래스에서는 execute메소드를 구현해 램프 켜는 기능을 구현한다. AlarmStartCommand역시 마찬가지다.
public interface Command {
public abstract void execute();
}
public class Button {
private Command command;
public Button(Command command) {
setCommand(command);
}
public void setCommand(Command command) {
this.command = command;
}
public void pressed() {
command.execute();
}
}
public class Lamp {
public void trunOn() {
System.out.println("Lamp On.");
}
}
public class LampOnCommand implements Command {
private Lamp lamp;
public LampOnCommand(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.trunOn();
}
}
public class Alarm {
public void start() {
System.out.println("Rrrrrr..!");
}
}
public class AlarmOnCommand implements Command {
private Alarm alarm;
public AlarmOnCommand(Alarm alarm) {
this.alarm = alarm;
}
@Override
public void execute() {
alarm.start();
}
}
public class Client {
public static void main(String[] args) {
Lamp lamp = new Lamp();
Command lampOnCommand = new LampOnCommand(lamp);
Button button1 = new Button(lampOnCommand);
button1.pressed();
Alarm alarm = new Alarm();
Command alarmOnCommand = new AlarmOnCommand(alarm);
Button button2 = new Button(alarmOnCommand);
button2.pressed();
button2.setCommand(lampOnCommand);
button2.pressed();
}
}
Command 인터페이스를 구현하는 LampOnCommand와 AlarmOnCommand 객체를 Button객체에 설정한다.
Button클래스의 pressed메소드에서 Command인터페이스의 execute 메소드를 호출할 수 있게 함으로써 LampOnCommand와 AlarmOnCommand 클래스의 execute메소드를 실행할 수 있다.
버튼을 눌렀을때 필요한 기능은 Command인터페이스를 구현한 클래스의 객체를 Button객체에 설정해서 실행할 수 있다.
여기서 LampOffCommand를 추가해보자.
public class Lamp {
public void trunOn() {
System.out.println("Lamp On.");
}
public void turnOff() { ///<<<<<<<<<<<<<<<
System.out.println("Lamp Off.");
}
}
public class LampOffCommand implements Command {
private Lamp lamp;
public LampOffCommand(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.turnOff();
}
}
public class Client {
public static void main(String[] args) {
Lamp lamp = new Lamp();
Command lampOffCommand = new LampOffCommand(lamp);
button1.setCommand(lampOffCommand);
button1.pressed();
}
}