All clients of Command objects treat each object as a "black box" by simply invoking the object's virtual execute() method whenever the client requires the object's "service".
A Command class holds some subset of the following: an object, a method to be applied to the object, and the arguments to be passed when the method is applied. The Command's "execute" method then causes the pieces to come together.
| Before | After | |
|---|---|---|
// BEFORE - the client has to query the "type" of
// each object, and manually invoke the desired
// method.
// AFTER - the desired method is encapsulated in
// each Command object.
class Giant {
public:
enum Type { Fee, Phi, Pheaux };
Giant() {
m_id = s_next++;
m_type = (Type) (m_id % 3);
}
Type get_type() { return m_type; }
void fee() { cout << m_id << "-fee "; }
void phi() { cout << m_id << "-phi "; }
void pheaux() { cout << m_id << "-pheaux "; }
private:
Type m_type;
int m_id;
static int s_next;
};
int Giant::s_next = 0;
template <typename T>
class Queue {
public:
Queue() { m_add = m_remove = 0; }
void enque( T* c ) {
m_array[m_add] = c;
m_add = (m_add + 1) % SIZE;
}
T* deque() {
int temp = m_remove;
m_remove = (m_remove + 1) % SIZE;
return m_array[temp];
}
private:
enum { SIZE = 8 };
T* m_array[SIZE];
int m_add, m_remove;
};
int main( void ) {
Queue<Giant> que;
Giant input[6], *bad_guy;
for (int i=0; i < 6; i++)
que.enque( &input[i] );
for (int i=0; i < 6; i++) {
bad_guy = que.deque();
if (bad_guy->get_type() == Giant::Fee)
bad_guy->fee();
else if (bad_guy->get_type() == Giant::Phi)
bad_guy->phi();
else if (bad_guy->get_type() == Giant::Pheaux)
bad_guy->pheaux();
}
cout << '\n';
}
// 0-fee 1-phi 2-pheaux 3-fee 4-phi 5-pheaux
|
class Giant {
public:
Giant() { m_id = s_next++; }
void fee() { cout << m_id << "-fee "; }
void phi() { cout << m_id << "-phi "; }
void pheaux() { cout << m_id << "-pheaux "; }
private:
int m_id;
static int s_next;
};
int Giant::s_next = 0;
class Command {
public:
typedef void (Giant::*Action)();
Command( Giant* object, Action method ) {
m_object = object;
m_method = method;
}
void execute() {
(m_object->*m_method)();
}
private:
Giant* m_object;
Action m_method;
};
template <typename T>
class Queue {
public:
Queue() { m_add = m_remove = 0; }
void enque( T* c ) {
m_array[m_add] = c;
m_add = (m_add + 1) % SIZE;
}
T* deque() {
int temp = m_remove;
m_remove = (m_remove + 1) % SIZE;
return m_array[temp];
}
private:
enum { SIZE = 8 };
T* m_array[SIZE];
int m_add, m_remove;
};
int main( void ) {
Queue<Command> que;
Command* input[] = { new Command( new Giant, &Giant::fee ),
new Command( new Giant, &Giant::phi ),
new Command( new Giant, &Giant::pheaux ),
new Command( new Giant, &Giant::fee ),
new Command( new Giant, &Giant::phi ),
new Command( new Giant, &Giant::pheaux ) };
for (int i=0; i < 6; i++)
que.enque( input[i] );
for (int i=0; i < 6; i++)
que.deque()->execute();
cout << '\n';
}
// 0-fee 1-phi 2-pheaux 3-fee 4-phi 5-pheaux
|
Chain of Responsibility can use Command to represent requests as objects. [GoF, p349].
Command and Memento act as magic tokens to be passed around and invoked at a later time. In Command, the token represents a request; in Memento, it represents the internal state of an object at a particular time. Polymorphism is important to Command, but not to Memento because its interface is so narrow that a memento can only be passed as a value. [GoF, p346]
Command can use Memento to maintain the state required for an undo operation. [GoF, p242]
MacroCommands can be implemented with Composite. [GoF, p242]
A Command that must be copied before being placed on a history list acts as a Prototype. [GoF, p242]
POSA's Command Processor pattern describes the scaffolding Command needs to support full multilevel undo and redo. [Vlissides, Java Report, Nov 2000, p80]
Two important aspects of the Command pattern: interface separation (the invoker is isolated from the receiver), time separation (stores a ready-to-go processing request that's to be stated later). [Alexandrescu, p101]