명령 (Command)
1. 의도
- 요청 자체를 캡슐화하는 것. 요청이 서로 다른 사용자를 매개변수로 만들고, 요청을 대기/로깅/되돌릴 수 있는 연산을 지원.
2. 활용
- 명령 패턴은 요청 자체를 객체화하여 명시되지 않은 응용프로그램 객체의 요청을 처리할 수 있도록 지원.
- Command 추상 클래스는 연산에 필요한 인터페이스를 선언한다.
- Command 클래스의 가장 기본적인 연산은 Execute()
- 서브 클래스는 생성자에서 수신 객체(피연산의 대상)을 매개변수로 받고 수신 객체와 관련된 요청을 호출하도록 Execute()를 구현한다.
- 메뉴 예시
Menu에서 각 선택 항목은 MenuItem 클래스의 인스턴스이다. Application은 메뉴와 메뉴를 구성하는 메뉴 항목(Menu Item)을 관리하고 열고 있는 Document 객체가 무엇인지 알고 있는 상황이다.
- MenuItem 인스턴스는 어떤 Command 인스턴스를 가지고 있어 Clicked() 호출시 해당 명령이 Execute()되도록 실행하는 구조이다.
- MenuItem은 구체 Command 클래스를 알 필요없이 요청을 수행할 수 있다.
1. PasteCommand
- 클립보드에 있는 내용을 문서로 붙이는 일을 담당. PastCommand 클래스의 요청을 받아 수행하는 객체는 Document 객체로 Exeucte() 연산은 처리할 Document 클래스에 정의된 Paste()를 호출하도록 구현한다.
2. OpenCommand
- 문서를 열기 위해 사용자에게 파일 이름을 먼저 물어보고 객체를 생성 응용프로그램에 추가한 뒤 해당 파일을 여는 순으로 구성되어 있다.
3. MacroCommand
- 여러 명령을 순차적으로 실행하는 흐름을 매크로처럼 지정하고 사용한다. Command 연산이 담겨있는 컨테이너에서 하나씩 순차적으로 호출하는 예를 볼 수 있다.
명령 패턴은 다음과 같은 일을 위해 사용한다.
- 수행 동작을 객체로 매개변수화하고자 할때, 즉 콜백 함수로 등록하였다가 나중에 호출하는 방식으로 매개변수화가 가능하다.
- 서로 다른 시간에 요청을 명시, 저장, 그리고 실행하고 싶을 때.
- 실행 취소 기능을 지원하고 싶을 때, Command의 Execute() 연산은 상태를 저장할 수 있는데, 지금까지 얻은 결과를 바꿀 수 있다.
- 시스템이 고장 났을 때 재적용 가능하도록 변경 과정에 대한 로깅을 지원하고 싶을 때. Command 인터페이스를 확장해서 Load()와 Store() 연산을 정의하면 상태의 변화를 지속적 저장소에 저장해 둘 수 있다.
- 기본적인 연산의 조합으로 상위 수준의 연산을 써서 시스템을 구조화 하고 싶을 때. 정보 시스템의 "트랜잭션"과 같은 상위 수준의 연산을 구현하고 데이터 관리를 수행.
3. 참여자
- Command: 연산 수행에 필요한 인터페이스를 선언한다.
- ConcreteCommand: Receiver 객체와 Action() 간의 연결성을 정의. 구체적 연산을 Execute()로 구현한다.
- Client: ConcreteCommand 객체를 생성하고 처리 객체로 정의한다.
- Invoker: 명령어에 처리를 수행할 것을 요청한다.
- Receiver: 요청에 관련된 연산 수행 방법을 알고 있다. Exeucte()에서 실제 실행 주체 객체.
4. 협력 방법
1. Invoker 클래스는 ConcretCommand 객체를 Receiver 인스턴스와 함께 초기화한다.
2. Invoker 클래스는 Command에 정의된 Execute()를 호출하여 요청을 발생시킨다. 명령어 취소 기능을 지원하고 싶다면 그 전에 호출 전 상태를 저장해 둔다. (StoreCommand())
3. ConcreteCommand 객체는 요청을 실제 처리할 객체에 정의된 연산을 호출한다.
- 실제 실행흐름을 보면 Execute()가 발생하고 Command에 포함된 Receiver가 Action()을 발생시키는 것을 볼 수 있다.
5. 결과
- Command 연산을 호출하는 객체(Invoker)와 연산 수행 방법을 구현하는 객체(Receiver)를 분리한다.
- Command는 다른 객체와 같은 방식으로 조작되고 확장 가능하다.
- 명령을 여러 개를 복합해서 복합 명령을 만들 수 있다.
- 새로운 Command 객체를 추가하기 쉽다.
6. 구현
1. 얼마나 지능적인 명령어를 구현할 것인가: 처리 요청을 수행하는 액션과 이를 받는 객체 사이의 연결 관계를 정의한다.
2. 취소 및 반복 연산을 지원할 것인가: 수행된 명령들을 저장하는 이력 목록을 남긴다.
3. 취소 진행 도중 오류가 누적되는 것을 피하기
4. C++ 템플릿 사용하기: Execute() 연산 내용이 템플릿화 할 수 있다면 템플릿을 사용한다.
예제 코드
Command.h
추상 클래스 Command를 정의한다. 가장 기본 연산 Execute() 인터페이스가 포함되어 있다.
class Command
{
public:
virtual ~Command();
virtual void Execute() = 0;
protected:
Command();
};
OpenCommand.h
다음 OpenCommand 클래스는 응용프로그램을 수신 객체로 설정하고 문서를 열었을 때 응용프로그램에 추가하기 위해 사용한다.
class OpenCommand : public Command
{
public:
OpenCommand(Application*);
virtual void Execute();
protected:
virtual const char* Askuser();
private:
Application* _application;
char* _response;
};
OpenCommand::OpenCommand(Application* a)
{
_application = a;
}
void OpenCommand::Execute()
{
const char* name = AskUser();
if (name != 0)
{
Document* document = new Document(name);
_application->Add(document);
document->Open();
}
}
PasteCommand.h
PasteCommand는 문서를 수신 객체로 받아 문서를 복하는 연산을 지원한다.
class PasteCommand : public Command
{
public:
PasteCommand(Document*);
virtual void Execute();
private:
Document* _document;
};
PasteCommand::PasteCommand(Document* doc)
{
_document = doc;
}
void PasteCommand::Execute()
{
_document->Paste();
}
SimpleCommand.h
C++ 템플릿을 사용하여 수신 객체 클래스를 템플릿화 하여 수신 객체 클래스의 메서드를 콜백 함수로 등록한다.
typedef void(Receiver::* Action) ();
-> void(Receiver::*)() 매개변수가 없고 반환값이 void인 Receiver 클래스 메서드를 Action으로 명명한다.
#include "Command.h"
template<class Receiver>
class SimpleCommand : public Command
{
public:
// Receiver 클래스의 Action 함수 객체를 위한 타입 명명
typedef void (Receiver::*Action)();
SimpleCommand(Receiver* receiver, Action action)
: _receiver(receiver), _action(action)
{
}
virtual void Execute();
private:
Action _action; // 저장할 명령 콜백 함수
Receiver _receiver; // 명령 피객체
};
template<class Receiver>
void SimpleCommand<Receiver>::Execute()
{
(_receiver->*_action)();
}
따라서 사용자 코드에서 다음과 같이 사용가능하다.
class MyClass {
public:
MyClass(Document* doc);
void Open();
};
int main()
{
MyClass* receiver = new MyClass(new Document("test.txt"));
Command* aCommand = new SimpleCommand<MyClass>(receiver, &MyClass::Open);
aCommand->Execute();
return 0;
}
MacroCommand.h
Macro 명령은 복합체 패턴으로도 구현가능한데 실행하는 명령들을 리스트로 관리하고 추가 삭제 기능을 제공한다.
Execute() 실행시 자신이 가지고 있는 명령들을 순차적으로 실행한다.
class MacroCommand : public Command
{
public:
MacroCommand();
virtual ~MacroCommand();
virtual void Add(Command*);
virtual void Remove(Command*);
virtual void Execute();
private:
List<Command*>* _cmds;
};
void MacroCommand::Execute()
{
ListIterator<Command*> i(_cmds);
for (i.First(); i.IsDone(); i.Next())
{
Command* c = i.CurrentItem();
c->Execute();
}
}
void MacroCommand::Add(Command* c)
{
_cmds->Append(c);
}
void MacroCommand::Remove(Command* c)
{
_cmds->Remove(c);
}
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 (4) 상태 State (0) | 2021.02.03 |
---|---|
[디자인 패턴] 행동 패턴 (3) 해석자 Interpreter (0) | 2021.02.02 |
[디자인 패턴] 행동 패턴 (1) 책임 연쇄 Chain of Responsiblity (0) | 2021.02.01 |
[디자인 패턴] 생성 패턴 정리 (0) | 2021.02.01 |
[디자인 패턴] 구조 패턴 정리 (0) | 2021.01.30 |