Applied Design Patterns with Java

Behavioral :: Command (233) {C ch 17}

Implementation

Here are implementation issues to consider when using the Command pattern:

  1. How intelligent should a Command be? A command can have a wide range of abilities. At one extreme it defines a binding between a receiver and the delegated actions that carry out the request. At the other extreme it implements everything itself without delegating to a receiver at all. The latter extreme is useful when you want to define commands that are independent of existing classes, when no suitable receiver exists, or when a command knows its receiver implicitly. A command that creates another application window may be just as capable of creating the window as any other object. Between these extremes are Commands that have enough knowledge to find their receiver dynamically.
  2. Supporting undo and redo. Commands can support undo and redo capabilities if they provide a way to reverse their execution (e.g., an Unexecute or Undo operation). A ConcreteCommand class might need to store additional state to do so. This state can include the Receiver object, which actually carries out operations in response to the request, the arguments to the operation performed on the receiver, and any original values in the Receiver that can change as a result of handling the request. The Receiver must provide operations that let the Command return the receiver to its prior state. To support one level of undo, an application needs to store only the command that was executed last. For multiple-level undo and redo, the application needs a history list of commands that have been executed, where the maximum length of the list determines the number of undo/redo levels. The history list stores sequences of commands that have been executed. Traversing backward through the list and reverse-executing commands cancels their effect; traversing forward and executing commands reexecutes them. An undoable Command might have to be copied before it can be placed on the history list. That's because the Command object that carried out the original request will perform other requests at later times. Copying is required to distinguish different invocations of the same command if its state can vary across invocations. A DeleteCommand that deletes selected objects must store different sets of objects each time it's executed. The DeleteCommand object must be copied following execution, and the copy is placed on the history list. If the Command's state never changes on execution, then copying is not required—only a reference to the command need be placed on the history list. A Command that must be copied before being placed on the history list acts as a Prototype (117).
  3. Avoiding error accumulation in the undo process. Hysteresis (the tendency of a system to behave differently depending on the direction of change of an input parameter) can be a problem in ensuring a reliable, semantics-preserving undo/redo mechanism. Errors can accumulate as Commands are executed, unexecuted, and reexecuted repeatedly so that an application's state eventually diverges from original values. It may be necessary to store more information in the Command to ensure that objects are restored to their original state. The Memento (283) pattern can be applied to give the Command access to this information without exposing the internals of other objects.
  4. Using C++ templates. This capability does not currently exist in Java.


Related Patterns

A Composite (163) can be used to implement MacroCommands.

A Memento (283) can keep state the Command requires to undo its effect.

A Command that must be copied before being placed on the history list acts as a Prototype (117).

Catalog Behavioral Prev Next