커맨드패턴은 정보 전문가(
information expert) 객체에게 요청을 맡기는 패턴으로 디자인 패턴중 행위 패턴에 해당한다.
- Command 패턴에서 중요한 책임을 가진 객체들은 총 4개로
Invoker,Receiver,Client, 그리고Command가 있다. Client는Context와 결합되어, 객체들을 생성하거나 오케스트레이션하여 진행하는 역할을 한다.Command는 요청을 캡슐화하고 요청을 실행할Reciever를 알고 있으며, 요청을 하는 역할을 한다.Invoker는 추상화된Command를 주입받을수 있고, 이를 실행하는 일을 하며,Template Method패턴으로서 다른 부수적인 책임을 수행하기도 한다Reciver는 실질적으로 요청을 실행하는 일을 한다.
- 단순히
Client와Server의 역할에서 벗어나 보다 구체적인 책임을 할당함에 따라,SRP를 준수하게된다. - 책임에 따라 작게 분할된 객체들은
OCP를 준수하기 더 쉬워진다 Invoker의 패턴에 따라 부수적인 다양한 기능을 구현하기 수월하다.Undo, Logging등
- 기존의 요청들을 조합하는 복잡한 요청이 새로 생겨도, 여러
Command를 조합하여 구현 가능하다.
- 단순히
Server와Client가 존재하는것에 비해 여러 계층이 추가되므로 코드가 복잡해진다.
public class Controller {
private final Service1 service1;
public Controller(Service1 service1) {
this.service1 = service1;
}
public void doService(){
service1.doIt();
}
}
public class Service1 {
public void doIt() {
System.out.println("do it! Service1");
}
}
public class Service2 {
public void doIt() {
System.out.println("do it! Service2");
}
}
간단한 Client(Controller) - Server(service) 구조의 수도코드를 통해 Command 패턴을 구현해보자.
public class Controller {
final CommandExecutor commandExecutor = new CommandExecutor();
final Service1 service1 = new Service1();
final Service2 service2 = new Service2();
public void startService1(){
commandExecutor.execute(new Service1StartCommand(service1));
}
public void endService1(){
commandExecutor.execute(new Service1EndCommand(service1));
}
public void startService2(){
commandExecutor.execute(new Service2StartCommand(service2));
}
public void endService2(){
commandExecutor.execute(new Service2EndCommand(service2));
}
public void cancelLastService(){
commandExecutor.revert();
}
}
public interface ServiceCommand {
void doService();
void unDoService();
}
public class Service1 {
public void start() {
System.out.println("Service1 start!");
}
public void end() {
System.out.println("Service1 end!");
}
}
public class Service1EndCommand implements ServiceCommand {
private final Service1 service1;
public Service1EndCommand(Service1 service1) {
this.service1 = service1;
}
@Override
public void doService() {
service1.end();
}
@Override
public void unDoService() {
new Service1StartCommand(this.service1).doService();
}
}
public class Service1StartCommand implements ServiceCommand {
private final Service1 service1;
public Service1StartCommand(Service1 service1) {
this.service1 = service1;
}
@Override
public void doService() {
service1.start();
}
@Override
public void unDoService() {
new Service1EndCommand(this.service1).doService();
}
}
public class CommandExecutor {
private final Stack<ServiceCommand> commandsHistory = new Stack<>();
public void execute(ServiceCommand command){
commandsHistory.push(command);
command.doService();
}
public void revert(){
ServiceCommand revertCommand = commandsHistory.pop();
revertCommand.unDoService();
}
}
기존 단순한 Client(Controller) - Server(Service) 구조에서 Command 패턴을 적용해보았다.
위는 간단한 예시지만 엔터프라이즈 애플리케이션에서도 일반적으로 사용되는 FACADE 패턴이 아니라 command 패턴으로 서비스 레이어를 구현하고 작업을 객체화시켜볼수도 있다.
작업을 되돌리거나 예약하거나 직렬화를 통해 로깅 및 관리하기 유용하기 때문에, 이벤트 드리븐 아키텍처에서 사가패턴을 적용할때 유즈케이스를 커맨드화 시켜 관리하는 방식도 고려해볼만 하다.
-
책임연쇄패턴, 커맨드패턴, 중재자패턴, 옵저버 패턴은 모두 요청에 대한 수신자와 송신자간 다양한 결합에 대한 패턴들이다.
- 책임연쇄패턴은 동적으로 연결된 체인에 요청을 순차적으로 전달하는 패턴이다.
- 커맨드 패턴은 수신자와 송신자를 오직 단방향으로만 연결하는 패턴이다.
- 중재자 패턴은 송신자와 수신자간 방향성을 제거하고 중재자 객체가 그 사이에서 소통을 관장한다.
- 옵저버 패턴은 수신자가 동적으로 구독/구독해제를 통해 메시지 수신을 결정할 수 있게한다.
-
커맨드 패턴과 메멘토 패턴을 사용하여
Undo기능을 구현할 수 있다. -
커맨드 패턴과 전략 패턴은 구조적으로 매우 유사하지만, 의도가 다르다.
- 전략패턴은 하나의
Context안에 다른 알고리즘이 필요할때 사용하는 패턴이지만, 커맨드 패턴은 기본적으로 요청을 객체화시켜 다루며, 여러 요청들을 큐나 스택 구조에 기록하거나 다루는데 특화되있다.
- 전략패턴은 하나의
