Command Pattern

실리콘·2023년 2월 24일
0

study of Design Patterns (1994). Diagrams are scanned from original text

Intent

Encapsulate request as an object, thereby letting you parameterize clients with difference requests, queue or log requests, and support undoable operations

Also Known As

Action, Transaction

Motivation

Sometimes it’s necessary to issue requests to objects without knowing anything about the operation being requested or the receiver of the request.

Here, Menuitem will have reference to a Command's instance, command. command.execute() will be called when this MenuItem is clicked.
But MenuItem doesn't need to know about details of Execute(). It suffices to know that it will cause some expected consequence.

Also, different kind of MenuItem will have reference to different type of Command objects. But it doesn't matter. MenuItem will just call MenuItem.Command.Execute().

Then Command subclasses will store reference to receiver of the request, and invoke operations on the receiver as needed. Ex. a PasteCommand could have reference to a Document object, which is supplied to PasteCommand at instantiation. When Execute() is called on PasteCommand, it will run some operations (maybe Document.Paste()) to receiver.

However, OpenCommand's Execute() will do different operations like below.

And sometimes, a MenuItem needs to execute a sequence of commands. Then we can create a macro, MacroCommand, which has a sequence of Command objects that runs in sequence. But this MacroCommand needn't keep a reference to receiver of commands, as each Command will have that.

In each of these examples, notice how the Command pattern decouples operation invoker object from operation performer object. This will give a lot of flexibility in desiging UI. (I guess this is lot like ReactJS hooks?). We can also change commands dynamically, especially for context-sensitive menus. We can also support command scripting by composing commands into larger ones.

All of this is possible because the object that issues a request only needs to know how to issue it; it doesn’t need to know how the request will be carried out.

Applicability

Use the Command Pattern when you want to:

  • Parameterize objects by an action to perform. You can express such parameterization in a procedural language with a callback function. Commands are object-oriented replacement for callbacks.
  • specify, queue, and execute requests at different times. A command object can have a lifetime independent of the original request. If the receiver of a request can be represented in an address-space independent way, then you can transfer a command object forthe request to a different process and fulfill the request there
  • support undo. Command.Execute() can store state for reversing its effects in the command itself. then Command interface must have sth like Unexecute(). Executed command are stored in a history list. You can have unlimited-level undo and redo if you want to, by traversing this history list of commands.
  • support logging changes so that they can be reapplied in case of a system crash. Augment Command interface w/ load/store operations, so you can keep a persistent log of changes. When it crashes, reload logged commands from disk and reexecute them in order. I guess this works in simple cases?
  • strucute a system around high-level operations built on primitives operations. Such a structure is common in information system that support transactions. A transaction encapsulates a set of changes to data. The Command pattern offers a way to model transaction. Commands have a common interface, letting you invoke all transactions the same way. Also makes it easy to extend the system with new transactions.

Structure

Participants

  • Command
    • declares an interface for executing an operation.
  • ConcreteCommand (PasteCommand, OpenCommand)
    • defines a binding between a Receiver object and an action.
    • Implements Execute() by invoking necessary operations on Receiver.
  • Client (Application)
    • creates a ConcreteCommand object and sets its receiver.
  • Invoker (MenuItem)
    • asks the command to carry out the request.
  • Receiver (Document, Application)
    • knows how to perform the operations with carrying out a request. Any object can be a receiver.

Collaborations

  • The client creates a ConcreteCommand object and specifies its receiver.
  • An Invoker object stores the ConcreteCommand object.
  • The invoker issues a command by doing Invoker.Command.Execute(). If commands are undoable, ConcreteCommand will store state for undoing the command prior to invoking Execute()
  • The ConcreteCommand object invokes operations on its receiver to carry out the request.

The following diagram shows the interactions between these objects. It illustrates how Command decouples the invoker from the receiver (and the request it carries out).

Consequences

  1. Command decouples invoker from receiver.
  2. Commands are first-class objects. They can be manipulated and extended like any other objects.
  3. You can assemble commands into a composite command. An example is the MacroCommand class described earlier. In general, composite commands are an instance of the Composite Pattern.
  4. Easy to add new commands, because you don't have to change existing classes.

Implementation

  1. How intelligent should a command be? One extreme can make command merely a binding between a receiver and the actions that carry out the request. Another extreme it implements everything itself w/o delegating to a receiver at all. The latter extreme is useful when you want to:
  • define commands that are independent of existing classes.
  • no suitable receiver exists
  • command knows its receiver explicitly.
    For example, a command that creates another application window may be just as capable of creating the window as any other object.
    Somewhere in between the two extremes are commands that have enough knowledege to find their receiver dynamically.
  1. Supporting undo and redo. Commands can support undo and redo capabilities if they provide a way to reverse their execution (ex. Unexecute(), Undo()). 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
  • 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.

Usually history list of commands that have been executed is kept, w/ reasonable size limit. Traverse thru the list and call Execute()/Unexecute() as needed.

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 invoacations of the same command if its state can vary across invocations.

For example, a DeleteCommand that deletes selected objects must store different sets of objects each time it's executed. Therefore the DeleteCommand object must be copied following execution, and the copy is placed on the history list. If the command's state doesn't change, keeping a reference to it in the object is enough. Commands that must be copied before being placed on the history list act as prototypes.

  1. Avoiding error accumulation in the undo process. Hysterisis can be a problem in ensuring a reliable semantics-preserving undo/redo mechanism. Erros 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 therefor to store more info in the command to ensure that objects are restored to their original state. The Memento Pattern can be applied to give the command access to this info w/o exposing the internals of other objects.

  2. Using C++ Templates. For commands that aren't undoable && don't require arguments. Shown in Sample Code section.

Sample Code

Book C++, me python.
not sure what using C++ templates meant above.

Known Uses

some papers, old examples. someting about functors, bojects that are functions.

Composite Pattern to implement MacroCommands
Memento Pattern to keep state the command requires for undoing operations.
Prototype Pattern for commands that must be copied before being placed on the history list.

profile
software engineer

0개의 댓글