Avijeet Dash and Satyabrata
Dash
Intent
Encapsulate a request as an object, thereby letting you parameterize clients
with different requests, queue or log requests, and support undoable operations.
In 30 Seconds
The catch here is "Encapsulate a request as an object". This
encapsulation helps in decoupling of sender from receiver of the request. A sender
is an object that invokes an operation, and a receiver is an object
that receives the request to execute a certain operation. With decoupling,
the sender has no knowledge of the receiver's interface. The term request
here refers to the command that is to be executed. The Command Pattern also
allows us to vary when and how a request is fulfilled. Therefore, a Command
Pattern provides us flexibility as well as extensibility.
In programming languages like C, function pointers are used to
eliminate giant switch statements. The object-oriented languages can use the
Command pattern to implement callbacks. The Command pattern lets invoker make
requests of unspecified application objects by turning the request itself to an
object. The object can be stored and passed around like other objects. The key
to this is pattern is Command interface, which declares the operations. In the
simplest form this interface includes an abstract Execute operation. Concrete
Command subclasses specify a receiver-action pair by storing the receiver as an
instance variable and by implementing Execute to invoke the request. The
receiver has the knowledge required to carry out the request.
Motivation
Let’s say we want to model the time evolution of a stock-trading program.
We need information on
- What needs to be done, e.g. queued requests, alarms, conditions for
action. - What is being done, e.g. which parts of a composite or distributed action
have been completed. - What has been done, e.g. a log of undoable operations.
In other words, we want a kind of Reflection where what is being described is
workflow, and not just instantaneous data.
Forces
The number and implementation of program operations can vary. Operations
should be undoable, e.g. if the request was mistaken or a failure occurred in
the middle of a composite action.
Additional functionality related to an operation, such as logging, should be
decoupled from the operation itself.
Solution
Explicitly represent units of work as Command objects. Units of work depend
on the application and may range from reading and writing individual variables
to reformatting a text document. The interface of a Command object can be as
simple as a DoIt() method. Extra methods can be used to support undo and redo.
Commands can be persistent and globally accessible, just like normal objects.
Commands are parameterized by the variable to be written, word to delete,
place to move a figure to, etc. These parameters should be abstract, to avoid
embedded references which would hinder logging and communicating a Command. For
example, variables should be referred to by name, not pointer, and words should
be referred to by position in the text.
For the Command to perform its own undo, it needs to store information
destroyed by the operation. For example, the old value of the variable, the
contents of the word being deleted, and the old position of the figure need to
be stored. The UndoIt() method can use this to back out of the operation. For
safety, the Command could also store whether it has been done or not.
Commands are sent to a Command Processor, which may queue, prioritize, and
distribute them among machines or threads. The Command Processor can take care
of logging the Commands for future undo. It can also schedule a Command to occur
at a particular time or under specific conditions.
When all Commands are logged, storing the program state is redundant, because
the program state at any moment in time can be reconstructed by replaying the
log up to that point. Thus program state is essentially a cache of the
computation stored the log. This offers a simple way to implement undo: blow
away the current state and reconstruct the state at a previous time. To speed up
this operation, the state can be periodically written, or checkpointed,
to the log. Starting with the last checkpoint and replaying the Commands since
then can then reconstruct state.
We can apply Command Pattern when we want to:
- Since Commands are encapsulated, their number and implementations can
vary. - Commands have independent lifetimes. Thus they can be queued, stored, or
transmitted. - Commands can be logged to support undo and redo, either because of user
error or system crash. Commands can encapsulate their own undo procedure, or
the log can be replayed to simulate undo. - Commands can be assembled into macros and scripts, which are also
Commands. This is one way to represent atomic transactions. The composite
Command registers its start and end in the log. If an error or failure
occurs in the middle, all Commands are undone up to the start marker. - A Command can play the role of a Strategy or callback, decoupling the
issuer of the Command from the Command's implementation. For example, a
button can initiate an arbitrary operation depending on what its Command
Strategy was. The same Command can come from several different sources.
Sample Code
The participants in this sample code are
- Command Interface
It declares an interface for executing an operation.
- BuyStockCommand and SellStockCommand
These concrete command classes define a binding between a Receiver
object(StockTrade class) and implement execute() by invoking the corresponding
operation(s) on Receiver.
- Invoker
Asks the Command to carry out the request
- Client
Creates a ConcreteCommand object and sets its receiver.
// Command.java. public interface Command { public abstract void execute ( ); } // Receiver class. StockTrade.java class StockTrade { public void buy() { System.out.println("You want to buy stocks"); } public void sell() { System.out.println("You want to sell stocks "); } } // Invoker. Invoker.java class Invoker { private Command buyCommand, sellCommand; public Invoker( Command bc, Command sc) { buyCommand=bc; sellCommand=sc; } void buy( ) { buyCommand. execute( ) ; } void sell( ) { sellCommand . execute( ); } } //ConcreteCommand Class. BuyStockCommand.java class BuyStockCommand implements Command { private StockTrade stock; public BuyStockCommand ( StockTrade st) { stock = st; } public void execute( ) { stock . buy( ); } } //ConcreteCommand Class. SellStockCommand.java class SellStockCommand implements Command { private StockTrade stock; public SellStockCommand ( StockTrade st) { stock = st; } public void execute( ) { stock . sell( ); } } // Client public class Client { public static void main(String<> args) { StockTrade stock=new StockTrade(); BuyStockCommand bsc = new BuyStockCommand(stock); SellStockCommand ssc = new SellStockCommand(stock); Invoker testInvoker = new Invoker( bsc,ssc); testInvoker.buy(); // Buy Shares testInvoker.sell(); // Sell Shares } }
How Command pattern makes a difference in this sample code?
An alternate to command pattern here is we can directly invoke the operations
on the Receiver object and use switch() block to decide the appropriate
operations. But using switch() block is a bad design and it’s practice is
always discouraged.
Complexities simplified
- Programs do many things, so there may be an excessive number of different
Commands. This can be avoided by defining all operations in terms of a small
number of kernel Commands, a so-called bottleneck design. The graphical
interface, command-line interface, and network interface all create the same
kernel Commands. Bottlenecks are commonly used in programming languages, where
most syntax is defined in terms of a smaller, kernel language (the extraneous
syntax being called "sugar"). Frameworks like ET++ use bottlenecks
to minimize the number of methods that need to be overridden in a subclass.
Some OpenDoc components create a bottleneck by sending OSA events to
themselves rather than directly invoking methods. This makes it easy to log
and override the actions of the component. - The undo operation can be a direct message to the Command Processor or can
be a Command of its own. In the former case, undo do not enjoy the advantages
of regular Commands, and have no audit trail. In the latter case, only a
single-level of undo is possible, because a second undo will actually be a
redo. - The above problems with undo can be avoided by using anti-Commands,
which are Commands that happen to undo previous Commands. For example, x--
is the anti-Command to x++.
A Command should be able to produce its own anti-Command. Anti-Commands allow
non-sequential undo, i.e. undoing a Command which wasn't the most recently
done. - Commands can be passed along a Chain of Responsibility for consumption.
- As Ralph Johnson points out in Transactions
and Accounts (PLoP'95),
sometimes the implementation of a Command needs to be changed or an operation
needs to be associated with a different Command. Such changes can also be
modeled as Commands, allowing them to be recorded and undone.
Related Patterns
The Factory Method pattern is sometimes used to provide a layer of
indirection between a user interface and command classes.
Memento can be used to keep state the command requires to undo its effect.
References:
- Gamma, E., Helm, R.,Johnson,R. & Vlissides, J. (1995).
Design Patterns: Elements of Reusable Object-Oriented Software. Reading Mass.,
Addison Wesley. - http://students.engr.scu.edu/~hto1/pattern.html
- http://www.javaworld.com/javaworld/javatips/jw-javatip68.html
- http://pandonia.canberra.edu.au/java/command.html
- http://www.theserverside.com/patterns/thread.jsp?thread_id=654