반응형
의도
- 요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴
- 요청 자체를 캡슐화하는 것. 이를 통해 요청이 서로 다른 사용자를 매개변수로 만들고, 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원한다.
장점
- 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.
- 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다.
- 커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다
단점
- 코드가 복잡하고 클래스가 많아진다.
알려진 사용 예
- 자바
- 쓰레드가 실행하는 Runnable 를 하나의 Command 라고 볼 수 있다.
- 스프링
- 스프링 JDBC의 SimpleJdbcInsert 클래스
- Insert 쿼리를 만들기 위해서 필요한 모든 방법을 가지고 있는 하나의 커맨드 오브젝트라고 볼 수 있다.
활용성
- 수행할 동작을 객체로 매개변수화하고자 할 때. 콜백(callback) 함수, 즉 어딘가 등록되었다가 나중에 호출되는 함수를 사용해서 이러한 매개변수하를 표현할 수 있다. 명령 패턴은 콜백을 객체지향방식으로 나타낸 것이다.
- 서로 다른 시간에 요청을 명시하고, 저장하며, 실행하고 싶을 때
- 실행 취소 기능을 지원하고 싶을 때
- 시스템이 고장 났을 때 재적용이 가능하도록 변경 과정에 대한 로깅을 지원하고 싶을 때
- 기본적인 연산의 조합으로 만든 상위 수준 연산을 써서 시스템을 구조화하고 싶을 때
결과
- Command는 연산을 호출하는 객체와 연산 수행 방법을 구현하는 객체를 분리한다.
- Command는 일급 클래스이다. 다른 객체와 같은 방식으로 조작되고 확장할 수 있다.
- 명령을 여러 개를 복합해서 복합 명령을 만들 수 있다.
- 복합체 패턴을 이용해서 여러 명령어를 구성할 수 있다.
- 새로운 Command 객체를 추가하기 쉽다. 기존 클래스(invoker)를 변경할 필요 없이 단지 새로운 명령어에 대응하는 클래스만 정의하면 된다.
협력 방법
- 사용자는 ConcreteCommand 객체를 생성하고 이를 수신자로 지정한다.
- Invoker 클래스는 ConcreteCommand 객체를 저장한다.
- Invoker 클래스는 Command에 정의된 Execute()를 호출하여 요청을 발생시킨다. 명령어가 취소 가능한 것이라면 ConcreteCommand는 이전에 Execute() 호출 전 상태의 취소 처리를 위해 저장한다.
- ConcreteCommand 객체는 요청을 실제 처리할 객체에 정의된 연산을 호출한다.
구조
실제 구현 구조
소스코드
//invoker
public class Button {
private Stack<Command> commands = new Stack<>();
public void press(Command command) {
command.execute();
commands.push(command);
}
public void undo() {
if (!commands.empty()) {
Command command = commands.pop();
command.undo();
}
}
//main 메서드는 Button 클래스 밖의 외부 코드라고 가정한다.
public static void main(String[] args) {
Button button = new Button();
button.press(new GameStartCommand(new Game()));
button.press(new LightOnCommand(new Light()));
button.undo();
button.undo();
}
}
//Command
public interface Command {
void execute();
void undo();
}
//receiver
public class Game {
private boolean isOn;
public void start() {
System.out.println("게임 시작");
this.isOn = true;
}
public void end() {
System.out.println("게임 종료");
this.isOn = false;
}
public boolean isOn() {
return this.isOn;
}
}
//receiver
public class Light {
private boolean isOn;
public void on() {
System.out.println("불을 켭니다.");
this.isOn = true;
}
public void off() {
System.out.println("불을 끕니다.");
this.isOn = false;
}
public boolean isOn() {
return this.isOn;
}
}
//ConcreteCommand
public class GameEndCommand implements Command {
private Game game;
public GameEndCommand(Game game) {
this.game = game;
}
@Override
public void execute() {
game.end();
}
@Override
public void undo() {
new GameStartCommand(this.game).execute();
}
}
//ConcreteCommand
public class GameStartCommand implements Command {
private Game game;
public GameStartCommand(Game game) {
this.game = game;
}
@Override
public void execute() {
game.start();
}
@Override
public void undo() {
new GameEndCommand(this.game).execute();
}
}
//ConcreteCommand
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
new LightOnCommand(this.light).execute();
}
}
//ConcreteCommand
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
new LightOffCommand(this.light).execute();
}
}
관련 패턴
- 여러 명령어가 구성된 MacroCommand를 구현하는 데 복합체 패턴을 사용할 수 있다.
- 취소를 처리할 때 객체의 상태를 관리하는 데 메멘토 패턴을 사용할 수 있다.
- 명령어가 처리되어 처리된 이력 목록에 저장되기 전에 명령어를 복사해야 한다면 원형 패턴을 사용할 수 있다.
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
반응형