
Command 패턴을 이해하기 위해 식당에 가서 음식을 주문한다고 생각해보자.

"우리: 웨이터님 저희 이렇게 주문할게요!"
"웨이터: 주문서를 보고는 ~렇게 주문하시는 거 맞으시죠?"
"우리: 네!"
"웨이터: 주방(요리사)에 주문서를 전달"
"요리사: 주문서를 보고는 ~렇게 만들어서 내야 겠다."
이를 객체의 관점에서 바라보면 다음과 같이 해석할 수 있다.

여기서 핵심은 웨이터가 주문의 내용 몰라도 주문서를 주방에 전달하기만 하면 요리사가 어떤 요리를 내야할지 알 수 있다는 것이다.

이 그림에서 각자의 역할을 잘 기억해보면서 아주 간단하게 개념만 녹인 예제를 작성하려고 한다.
class Command {
public:
virtual void execute( );
};
먼저 고객의 요청사항을 구현할(위 그림의 고객의 주문 사항(스테이크, 파스타)) execute 메서드가 있는 Command 인터페이스를 만든다.
C++에서는 interface의 개념이 따로 없으므로 순수 가상 함수를 만든다. 왜냐하면 순수 가상 함수가 있는 클래스를 상속 시 반드시 구현(implement)해야 하기 때문!
그리고 고객의 요청 사항을 이행할 객체가 필요하다. 예를 들어 위 예시처럼 스테이크와 파스타를 만든다고 해보면 다음과 같이 코드를 작성할 수 있다.
class Cook {
public:
Steak makeSteak(){ }
Pasta makePasta(){ }
};
중요한 점은 요청하는 객체와 요청 받는 객체의 분리다. 따라서 웨이터의 존재가 필요하고 웨이터가 주문서를 가져다가 요리사에게 주문서를 전달할 것이다.
class OrderCommand : public Command {
private:
Cook* cook;
public:
OrderCommand(Cook* cook) { this->cook = cook; }
void execute( ){
cook->makeSteak( );
cook->makePasta( );
}
};
주문서에는 스테이크랑 파스타가 들어있고
class Waitress {
private:
Command* command;
public:
Waitress(Command* command){ this->command = command; }
void execute( ){
command->execute();
}
};
웨이터는 주문서에 뭐가 적힌지는 모르지만 요리사에게 전달한다.
command->execute(); 이 부분인데,
주문서에 어떤 내용이 있는지는 모르지만 command 인터페이스의 execute메서드를 호출하면 Command타입의 포인터 변수 command에는 Command 인터페이스를 구현한 객체의 주소(위의 OrderCommand 객체)가 있다.
그래서 그 객체의 주소에 접근해서 cook의 makeSteak와 makePasta 메서드를 호출하는 것이다.
int main( ){
Cook* cook = new Cook();
Command* orderCommand = new OrderCommand(cook);
Waitress waitress = Waitress(orderCommand);
// 요리사가 스테이크, 파스타 만듦.
waitress.execute();
};
이렇게 waiter가 주문서(command)를 전달함으로써 고객이랑 요리사랑 직접 소통하지 않아도 요청사항(스테이크, 파스타)을 이행(execute)할 수 있게 된다.

위 소스 코드를 클래스 다이어그램으로 나타내보면 다음과 같다. 웨이터는 Command 인터페이스를 통해 execute 메서드를 호출하고
command 포인터에는 OrderCommand 객체의 주소가 있고 이에 접근하여 execute( )를 실행하므로 Cook에게 "스테이크랑 파스타 만들면 됨." 이라고 호출할 수 있다.
이렇게 Command를 통해 Invoker(요청하는 객체)와 Receiver(요청을 이행하는 객체)를 분리하는 것이다.
고객의 요청사항(Command)을 캡슐화하여 웨이터(Invoker)에게 전달하고 그 세부 내용을 알지 못해도 원할 때 그 요청 사항을 호출하면(Command의 execute( )) 요리사(Receiver)에게 요청이 가서 원하는 결과를 만들 수 있다.
Q. Command 패턴을 사용하면 작업을 요구한 invoker와 작업을 처리하는 receiver가 어떻게 분리되는 걸까요?
- invoker는 요청(command)의 인터페이스와 대화하기 때문!
- 즉, 컴파일 때 invoker는 요청의 인터페이스의 execute 메서드를 호출함.
- 하지만 런타임 때는 Command 타입의 포인터 변수에 저장된 주소(실제 command)에 접근해 execute( )를 호출함.
- 그 실제 command의 execute( )메서드에 Receiver가 해야할 일을 선언하기 때문에 invoker는 실제 어떤 receiver가 무슨 일을 하는지 모름.
여기서 중요 포인트는 다음과 같다.
- Q. 웹 서버에서 큐를 어떻게 활용할 수 있을까?
- 메인 스레드가 HTTP 요청을 받아서 큐에 넣어두고 별도의 워크 스레드가 해당 큐에서 요청을 꺼내서 처리
- Q. 큐를 사용할 수 있는 다른 예로는 어떤 것이 있을까?
- UI 이벤트 처리 등등..
Q. 잠깐 Queue(큐)는 뭐임?
- 즉, 배열의 형태로 First와 Last(Rear)라는 포인터를 가지고 있음. 선입선출(먼저 들어온 데이터가 먼저 나간다)로 다음으로 나갈 놈을 first라는 포인터가 가리키고 있고 제일 마지막에 들어온 놈을 Rear라는 포인터가 가리키고 있음.
만약에- 1) 만약에 1을 꺼내면? First 포인터가 다음 나갈 놈인 2를 가리킴
- 2) 만약에 5가 들어오면? 마지막에 나갈 놈인 4에서 5를 rear포인터가 가리킴.
요청하는 객체와 요청을 수행하는 객체를 분리하고 싶을 때 커맨드 패턴을 사용한다.
더 자세한 예시를 원한다면 다음 커맨드 패턴 Repository 를 참고하셔도 됩니다