Command
Intent
- Encapsulate a request as an object, thereby letting you parameterize
clients with different requests, queue or log requests, and support
undoable operations. [GoF, p233]
- Promote "invocation of a method on an object" to full object status
- An object-oriented callback
Problem
Need to issue requests to objects without knowing anything about the
operation being requested or the receiver of the request.
Structure Summary
Structure category: promote X to "full object status"
Similar patterns:
Prototype
Iterator
Mediator
Memento
Discussion
Command decouples the object that invokes the operation from the one
that knows how to perform it. To achieve this separation, the designer
creates an abstract base class that maps a receiver (an object) with an
action (a pointer to a member function). The base class contains an
execute() method that simply calls the action on the receiver.
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.
Sequences of Command objects can be assembled into composite (or macro)
commands.
Structure
The client that creates a command is not the same client that executes
it. This separation provides flexibility in the timing and sequencing
of commands. Materializing commands as objects means they can be
passed, staged, shared, loaded in a table, and otherwise instrumented
or manipulated like any other object. [Grand, p279]
Command objects can be thought of as "tokens" that are created by
one client that knows what need to be done, and passed to another
client that has the resources for doing it.
Example
The Command pattern allows requests to be encapsulated as objects,
thereby allowing clients to be parameterized with different requests.
The "check" at a diner is an example of a Command pattern. The waiter
or waitress takes an order or command from a customer and encapsulates
that order by writing it on the check. The order is then queued for a
short order cook. Note that the pad of "checks" used by each waiter is
not dependent on the menu, and therefore they can support commands to
cook many different items. [Michael Duell, "Non-software examples of
software design patterns", Object Magazine, Jul 97, p54]
Check list
- Define a Command interface with a method signature like execute().
- Create one or more derived classes that encapsulate some subset of the following: a "receiver" object, the method to invoke, the arguments to pass.
- Instantiate a Command object for each deferred execution request.
- Pass the Command object from the creator (aka sender) to the invoker (aka receiver).
- The invoker decides when to execute().
Before and after
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 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 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
|
Rules of thumb
Chain of Responsibility, Command, Mediator, and Observer, address how
you can decouple senders and receivers, but with different trade-offs.
Command normally specifies a sender-receiver connection with a
subclass.
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]
Notes
"Memento-Command" - a new pattern by John Vlissides. Presented in
Java Report, November 2000, pp70-80. Intent: manage undo state
when a command can affect unforseen receivers.