커맨드 패턴
커맨드 패턴은 이벤트가 발생했을 때 수행하는 기능이 다양하거나 변경이 필요한 경우에, 이벤트를 수행하는 클래스를 캡슐화하여 이벤트를 발생시키는 클래스에는 변경이 없도록 하는 패턴이다.
커맨드 패턴에서 나타나는 역할이 수행하는 작업은 다음과 같다.
•
Command : 실행될 기능에 대한 인터페이스
•
ConcreteCommand : 실행되는 기능에 대한 구현
•
Invoker : 기능 실행을 요청하는 호출자 클래스
•
Receiver : ConcreteCommand의 기능 실행을 위해 사용하는 수신자 클래스
커맨드 패턴을 순차 다이어그램으로 모델링하면 위와 같다.
만능 버튼 만들기
눌리면 특정 기능을 수행하는 버튼에 여러 기능을 수행할 수 있도록 만드는 예시를 통해 커맨드 패턴을 구현해보자.
램프 구현
먼저 버튼을 누르면 램프가 켜지는 기능을 구현해보자.
public class Lamp {
public void turnOn() {
// light on
}
}
Java
복사
public class Button {
private final Lamp lamp;
public Button(final Lamp lamp) {
this.lamp = lamp;
}
public void pressed() {
lamp.turnOn();
}
}
Java
복사
간단하게 구현하면 이처럼 구현할 수 있을 것이다.
문제점
만약 여기서 버튼을 누르면 램프를 켜는 대신 알람이 시작되게 하려면 어떻게 해야할까?
public class Button {
private final Alarm alarm;
public Button(final Alarm alarm) {
this.alarm = alarm;
}
public void pressed() {
alarm.turnOn();
}
}
Java
복사
Button의 내부 필드로 Lamp 대신 새로이 Alaram을 가지고 있어야하고, pressed() 메서드에도 변경이 필요하다. 추가적으로 Button에 상태를 두고, 상태에 따라 불이 켜지거나 알람이 켜지도록 만들고 싶다면 추가적인 다른 수정이 필요하다. 기능을 변경하거나 추가하기 위해 Button 클래스를 수정하는 것은 OCP 위반이다.
해결책
기능이 변경되거나 추가되어도 Button 클래스에 변경이 생기지 않게하려면, 변경되는 지점인 pressed 메서드의 구체적인 기능을 직접 구현하지 말고 이를 캡슐화하는 것이다.
public class Button {
private Command command;
public Button(final Command command) {
this.command = command;
}
public void setCommand(final Command command) {
this.command = command;
}
public void pressed() {
command.execute();
}
}
Java
복사
public interface Command {
void execute();
}
Java
복사
public class LampCommand implements Command {
private final Lamp lamp;
public LampCommand(final Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.turnOn();
}
}
Java
복사
public class AlarmCommand implements Command {
private final Alarm alarm;
public AlarmCommand(final Alarm alarm) {
this.alarm = alarm;
}
@Override
public void execute() {
alarm.start();
}
}
Java
복사
이렇게 Command 인터페이스로 추상화하고, 이를 구현하는 LampCommand와 AlarmCommand에서 각각 Lamp와 Alarm의 기능을 구현하는 방식이다. 이와 같이 작성하면 새로운 기능이 추가되거나 변경되어도 그 책임을 기능을 구현하는 커맨드만 변경하거나 추가하면 되어 OCP를 만족하며 설계할 수 있다.
만약 램프를 켜는 기능과 끄는 기능을 만들고 싶다면,
이와 같이 구성될 수 있는 것이다.