U E D R , A S I H C RSS

Gof/Command


1. Command


1.1. Intent

request 를 객체로 캡슐화 시킴으로서 다른 request들을 가진 클라이언트를 인자화시키거나, request를 queue하거나 대기시키며, undo가 가능한 명령을 지원한다.

1.2. Also Known As

Action, Transaction

1.3. Motivation

때때로 요청받은 명령이나 request를 받는 객체에 대한 정보없이 객체들에게 request를 넘겨줄 때가 있다. 예를 들어 user interface tookit은 button이나 menu처럼 사용자 입력에 대해 응답하기 위해 요청을 처리하는 객체들을 포함한다. 하지만, 오직 toolkit을 사용하는 어플리케이션만이 어떤 객체가 어떤일을 해야 할지 알고 있으므로, toolkit은 button이나 menu에 대해서 요청에 대해 명시적으로 구현을 할 수 없다. toolkit 디자이너로서 우리는 request를 받는 개체나 request를 처리할 operations에 대해 알지 못한다.

Command Pattern은 request 를 객체화함으로서 toolkit 객체로 하여금 불특정한 어플리케이션 객체에 대한 request를 만들게 한다. 이 객체는 다른 객체처럼 저장될 수 있으며 pass around 가능하다. 이 pattern의 key는 수행할 명령어에 대한 인터페이스를 선언하는 추상 Command class에 있다. 이 인터페이스의 가장 단순한 형태에서는 추상적인 Execute operation을 포함한다. 구체화된 Command subclass들은 request에 대한 receiver를 instance 변수로 저장하고 request를 invoke하기 위한 Execute operation을 구현함으로서 receiver-action 짝을 구체화시킨다. The receiver has the knowledge required to carry out the request.


Menu는 쉽게 Command Object로 구현될 수 있다. Menu 의 각각의 선택은 각각 MenuItem 클래스의 인스턴스이다. Application 클래스는 이 메뉴들과 나머지 유저 인터페이스에 따라서 메뉴아이템을 구성한다. Application 클래스는 유저가 열 Document 객체의 track을 유지한다.

어플리케이션은 각각의 구체적인 Command 의 subclass들로 각가각MenuItem 객체를 설정한다. 사용자가 MenuItem을 선택했을때 MenuItem은 메뉴아이템의 해당 명령으로서 Execute oeration을 호출하고, Execute는 실제의 명령을 수행한다. MenuItem객체들은 자신들이 사용할 Command의 subclass에 대한 정보를 가지고 있지 않다. Command subclass는 해당 request에 대한 receiver를 저장하고, receiver의 하나나 그 이상의 명령어들을 invoke한다.

예를 들어 PasteCommand는 clipboard에 있는 text를 Document에 붙이는 기능을 지원한다. PasteCommand 의 receiver는 인스턴스화할때 설정되어있는 Docuemnt객체이다. Execute 명령은 해당 명령의 receiver인 Document의 Paste operation 을 invoke 한다.


OpenCommand의 Execute operation은 다르다. OpenCommand는 사용자에게 문서 이름을 물은뒤, 대응하는 Document 객체를 만들고, 해당 문서를 여는 어플리케이션에 문서를 추가한 뒤 (MDI를 생각할것) 문서를 연다.


때때로 MenuItem은 연속된 명령어들의 일괄수행을 필요로 한다. 예를 들어서 해당 페이지를 중앙에 놓고 일반크기화 시키는 MenuItemCenterDocumentCommand 객체와 NormalSizeCommand 객체로 만들 수 있다. 이러한 방식으로 명령어들을 이어지게 하는 것은 일반적이므로, 우리는 복수명령을 수행하기 위한 MenuItem을 허용하기 위해 MacroCommand를 정의할 수 있다. MacroCommand는 단순히 명령어들의 sequence를 수행하는 Command subclass의 구체화이다. MacroCommandMacroCommand를 이루고 있는 command들이 그들의 receiver를 정의하므로 명시적인 receiver를 가지지 않는다.


이러한 예들에서, 어떻게 Command pattern이 해당 명령을 invoke하는 객체와 명령을 수행하는 정보를 가진 객체를 분리하는지 주목하라. 이러함은 유저인터페이스를 디자인함에 있어서 많은 유연성을 제공한다. 어플리케이션은 단지 menu와 push button이 같은 구체적인 Command subclass의 인스턴스를 공유함으로서 menu 와 push button 인터페이스 제공할 수 있다. 우리는 동적으로 command를 바꿀 수 있으며, 이러함은 context-sensitive menu 를 구현하는데 유용하다. 또한 우리는 명령어들을 커다란 명령어에 하나로 조합함으로서 command scripting을 지원할 수 있다. 이러한 모든 것은 request를 issue하는 객체가 오직 어떻게 issue화 하는지만 알고 있으면 되기때문에 가능하다. request를 나타내는 객체는 어떻게 request가 수행되어야 할지 알 필요가 없다.

1.4. Applicability

다음과 같은 경우에 CommandPattern을 이용하라.

  • MenuItem 객체가 하려는 일을 넘어서 수행하려는 action에 의해 객체를을 인자화시킬때. 프로그래머는 procedural language에서의 callback 함수처럼 인자화시킬 수 있다. Command는 callback함수에 대한 객체지향적인 대안이다.

  • 다른 시간대에 request를 구체화하거나 queue하거나 수행하기 원할때. Command 객체는 request와 독립적인 lifetime을 가질 수 있다. 만일 request의 receiver가 공간 독립적인 방법으로 (네트워크 등) 주소를 표현할 수 있다면 프로그래머는 request에 대한 Command 객체를 다른 프로세스에게 전달하여 처리할 수 있다.

  • undo 기능을 지원하기 원할때. Command의 Execute operation은 해당 Command의 효과를 되돌리기 위한 state를 저장할 수 있다. Command 는 Execute 수행의 효과를 되돌리기 위한 Unexecute operation을 인터페이스로서 추가해야 한다. 수행된 command는 history list에 저장된다. history list를 앞 뒤로 검색하면서 Unexecute와 Execute를 부름으로서 무제한의 undo기능과 redo기능을 지원할 수 있게 된다.

  • logging change를 지원하기 원할때. logging change 를 지원함으로서 시스템 충돌이 난 경우에 대해 해당 command를 재시도 할 수 있다. Command 객체에 load 와 store operation을 추가함으로서 change의 log를 유지할 수 있다. crash로부터 복구하는 것은 디스크로부터 logged command를 읽어들이고 Execute operation을 재실행하는 것은 중요한 부분이다.

  • 기본명령어들를 기반으로 이용한 하이레벨의 명령들로 시스템을 조직할 때. 그러함 조직은 transaction을 지원하는 정보시스템에서 보편화된 방식이다. transaction은 데이터의 변화의 집합을 캡슐화한다. CommandPattern은 transaction을 디자인하는 하나의 방법을 제공한다. Command들은 공통된 인터페이스를 가지며, 모든 transaction를 같은 방법으로 invoke할 수 있도록 한다. CommandPattern은 또한 새로운 transaction들을 시스템에 확장시키기 쉽게 한다.

1.6. Participants

  • Command
    - 수행할 operation을 위한 인터페이스를 선언한다.
  • ConcreteCommand (PasteCommand, OpenCommand)
    - Receiver 객체와 action 묶음을 정의한다.
  • Client (Application)
    - ConcreteCommand 객체를 만들고 receiver로 정한다.
  • Invoker (MenuItem)
    - command 에게 request를 수행하도록 요청한다.
  • Receiver (Document, Application)
    - 처리할 request에 대해 명령어들을 어떻게 수행해야 할지 알고 있다. 어떠한 클래스든지 Receiver로서 활동가능하다.

1.7. Collaborations

  • client는 ConcreteCommand 객체를 만들고, receiver를 정한다.
  • Invoker 객체는 ConcreteCommand객체를 저장한다.
  • invoker는 command에서 Execute를 호출함으로서 request를 issue한다. 명령어가 undo가능할때, ConcreteCommand는 명령어를 undo하기 위한 state를 저장한다.
  • ConcreteCommand 객체는 request를 처리하기 위해 receiver에서 operation을 invoke한다.

다음의 다이어그램은 이 객체들이 어떻게 상호작용하는지 보여준다. 이 다이어그램은 또한 어떻게 Command 가 receiver와 처리할 request로부터 invoker를 분리하는지 설명한다.


1.8. Consequences


CommandPattern은 다음과 같은 결과를 가져온다.
  1. Command는 해당 객체의 명령을 invoke하는 객체와 어떻게 수행해야 할지 알고 있는 객체와의 결합을 해제한다.
  2. Command는 첫번째단계의 클래스 객체이다. Command는 조작되어질 수 있으면서 다른 객체들과 마찬가지로 확장가능하다.
  3. Command를 묶을 수 있다. 앞서 설명한 MacroCommand가 그 예가 된다. 일반적으로 composite command들은 CompositePattern의 인스턴스이다.
  4. 새로운 Command를 추가하기가 쉽다. 왜냐하면 이미 존재하고있는 클래스들을 고칠 필요가 없기 때문이다.

1.9. Implementation


1.10. Sample Code

여기 보여지는 C++ code는 Motivation 섹션의 Command 크래스에 대한 대강의 구현이다. 우리는 OpenCommand, PasteCommandMacroCommand를 정의할 것이다. 먼저 추상 Commmand class 는 이렇다.
~cpp 
class Command {
public:
	virtual ~Command ();

	virtual void Execute () = 0;
protected:
	Command ();
};

OpenCommand는 유저로부터 제공된 이름의 문서를 연다. OpenCommand는 반드시 Constructor에 Application 객체를 넘겨받아야 한다. AskUser 는 유저에게 열어야 할 문서의 이름을 묻는 루틴을 구현한다.

~cpp 
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 는 receiver로서 Document객체를 넘겨받아야 한다. receiver는 PasteCommand의 constructor의 parameter로서 받는다.
~cpp 
class PasteCommand : public Command {
public:
	PasteCommand (Document*);

	virtual void Execute ();
private:
	Document* _document;
};

PasteCommand::PasteCommand (Document* doc) {
	_document = doc;
}

void PasteCommand::Execute () {
	_document->Paste ();
}

undo 할 필요가 없고, 인자를 요구하지 않는 단순한 명령어에 대해서 우리는 command의 receiver를 parameterize하기 위해 class template를 사용할 수 있다. 우리는 그러한 명령들을 위해 template subclass인 SimpleCommand를 정의할 것이다. SimpleCommand는 Receiver type에 의해 parameterize 되고

~cpp 
template <class Receiver>
class SimpleCommand : public Command {
public:
	typedef void (Receiver::* Action) ();

	SimpleCommand (Receiver* r, Action a) : 
		_receiver (r), _action (a) { }

	vitual void Execute ();

private:
	Action _action;
	Receiver* _receiver;
};

constructor는 receiver와 instance 변수에 대응되는 action을 저장한다. Execute는 단순히 action을 receiver에 적용한다.
~cpp 
template <class Receiver>
void Simplecommand<Receiver>::Execute () {
	(_receiver->*_action) ();

MyClass의 instance로 있는 Action을 호출할 command를 만들기 위해서, 클라이언트는 단순히 이렇게 코딩한다.
~cpp 
MyClass* receiver = new MyClass;
// ...
Command* aCommand = 
	new SimpleCommand<MyClass> (receiver, &MyClass::Action);
// ...
aCommand->Execute ();

이 방법은 단지 단순한 명령어에대한 해결책일 뿐임을 명심하라. track을 유지하거나, receiver와 undo state를 argument 로 필요로 하는 좀더 복잡한 명령들은 Command의 subclass를 요구한다.

MacroCommand는 부명령어들의 sequence를 관리하고 부명령어들을 추가하거나 삭제하는 operation을 제공한다. subcommand들은 이미 그들의 receiver를 정의하므로 MacroCommand는 명시적인 receiver를 요구하지 않는다.

~cpp 
class MacroCommand : public Command {
public:
	MacroCommand ();
	virtual ~MacroCommand ();

	virtual void Add (Command*);
	virtual void Remove (Command*);

	virtual void Execute ();

private:
	List<Command*>* _cmds;
};

MacroCommand의 열쇠는 Execute 맴버함수에 있다. 이것은 모든 부명령어들을 탐색하면서 그들 각각의 Execute operation를 수행한다.

~cpp 
void MacroCommand::Execute () {
	ListIterator<Command*> i (_cmds);
	
	for (i.First (); !i.IsDone (); i.Next()) {
		Command* c = i.CurrentItem ();
		c->Execute ();
	}
}


MacroCommand 가 Unexecute operation을 구현하기 위해서 MacroCommand의 부명령어들은 Execute operation에서 구현된 순서의 역순으로 참조되면서 unexecute해야 함을 숙지하자.

최종적으로 MacroCommand 는 부명령어들을 관리할 operation을 제공해야한다. MacroCommand는 또한 MacroCommand를 이루는 부명령어들을 삭제할 책임을 진다.

~cpp 
void MacroCommand::Add (Command* c) {
	_cmds->Append (c);
}

void MacroCommand::Remove (Command* c) {
	_cmds->Remove (c);
}


1.11. Known Uses

아마도 CommandPattern에 대한 첫번째 예제는 Lieberman 의 논문(Lie85)에서 나타났을 것이다. MacApp App89 는 undo가능한 명령의 구현을 위한 command의 표기를 대중화시켰다. ET++WGM88, InterViews LCI+92, UnidrawVL90 역시 CommandPatter에 따라 클래스들을 정의했다. InterViews는 각 기능별 명령에 대한 Action 추상 클래스를 정의했다. 그리고 action 메소드에 의해 인자화됨으로서 자동적으로 command subclass들을 인스턴스화 시키는 ActionCallback 템플릿도 정의하였다.

THINK 클래스 라이브러리 Sym93b 또한 undo 가능한 명령을 지원하기 위해 CommandPattern을 사용한다. THINK 에서의 Command들은 "Tasks" 로 불린다. Task 객체들은 ChainOfResponsibilityPattern에 입각하여 넘겨지고 소비되어진다.

AddMe)

1.12. Related Patterns

CompositePatternMacroCommand를 구현하는데 이용될 수 있다.

MementoPattern 은 undo를 위한 state를 유지할 수 있다.

history list 에 위치하기 전에 복사되여야 할 command는 Prototype으로서 작용한다.



Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:23:18
Processing time 0.0838 sec