Design Pattern----17.Behavioral.Command.Pattern (CSharp Sample)

Intent

  • Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
  • 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.

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.

Command scheme

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.

Command example

Check list

  1. Define a Command interface with a method signature like execute().
  2. Create one or more derived classes that encapsulate some subset of the following: a “receiver” object, the method to invoke, the arguments to pass.
  3. Instantiate a Command object for each deferred execution request.
  4. Pass the Command object from the creator (aka sender) to the invoker (aka receiver).
  5. The invoker decides when to execute().

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.
  • 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.
  • Command can use Memento to maintain the state required for an undo operation.
  • MacroCommands can be implemented with Composite.
  • A Command that must be copied before being placed on a history list acts as a Prototype.
  • 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).

Command in Delphi

This session consists of the development of a small application to read and pretty-print XML and CSV files. Along the way, we explain and demonstrate the use of the following patterns: State, Interpreter, Visitor, Strategy, Command, Memento, and Facade.

Encapsulate a request as an object, thereby letting you parameterise clients with different requests, queue or log requests, and support undoable operations.

The Command pattern is implemented in Delphi (since Delphi 4) in actions. We won’t use them, as I want to concentrate on the pattern. Actions are complex beasts, and this complexity might detract attention away from the points I want to make. There may well also be occasions where actions are not appropriate, so you may like to know how to roll your own Command structure.

We’re going to use Command for the last of the above options, to implement an undo/redo list, and also to shield our user interface code from having to know anything about documents or the operations on them. After we’ve discussed how to do that, you should be able to see how you could add logging, queues, and so on.

In its simplest form, the Command pattern consists of an abstract base class with an Execute method. Concrete subclasses are declared for each possible action, and the method implemented in each so as to carry out that action. Usually this requires a call to another class’s interface. For instance, in our example, one command would be to open a file. After prompting for a file name, the open command will call a document object’s OpenFile method. This second object is called the receiver. The object that asks the command to carry out a request is called the invoker. This is often something like a menu item or a button. We are going to do something a little different, though, as we’ll see later.

The declaration of our command class (in DocumentCommands.pas) is:

  1: TDocumentCommand = class(TObject)
  2: private
  3:   FDocument : TDocument;
  4: protected
  5:   procedure DoExecute; virtual; abstract;
  6:   procedure DoRollback; virtual;
  7:  
  8:   // Used Self Encapsulate Field refactoring here. Now descendant commands
  9:   // can access the document, even if they are declared in other units
 10:   property Document : TDocument read FDocument write FDocument;
 11: public
 12:   constructor Create(ADocument : TDocument);
 13:  
 14:   procedure Execute;
 15:   procedure Rollback; // Reverse effect of Execute
 16: end;
 17: 
 18: The implementation is: 
 19: constructor TDocumentCommand.Create(ADocument : TDocument);
 20: begin
 21:   inherited Create;
 22:   FDocument := ADocument;
 23: end;
 24: 
 25: procedure TDocumentCommand.DoRollback;
 26: begin
 27: end;
 28: 
 29: procedure TDocumentCommand.Execute;
 30: begin
 31:   if Assigned(FDocument) then begin
 32:     DoExecute;
 33:   end;
 34: end;
 35: 
 36: procedure TDocumentCommand.Rollback;
 37: begin
 38:   if Assigned(FDocument) then begin
 39:     DoRollback;
 40:   end;
 41: end;

Let’s examine what’s going on here. We have two public methods, one to perform a command, and another to reverse it. We need this last one because we want to support undo. Note that both these methods check to see if the document is available before calling another, protected, method. This is a very simple example of the Template pattern. We don’t want programmers who are implementing new commands to have to remember to do that, so we will do it for them here, and they need to override the DoExecute and DoRollback methods instead.

The DoRollback method implementation is empty because a command might not be able to be undone (we are only going to support this on pretty printing and search-and-replace), so we don’t want to force subclasses to implement it by making the method abstract. However, we do want the command to be implemented, so the DoExecute method is abstract to force it to be overridden.

The constructor gets the document passed as a parameter, and that will be used as the receiver in our example. For instance, the Open command is implemented as follows:

  1: procedure TOpenCommand.DoExecute;
  2:   var
  3:     FileName : string;
  4: begin
  5:   if PromptForFileName(FileName,'XML files (*.xml)|*.xml|CSV files (*.csv)|*.csv') then begin
  6:     FDocument.OpenFile(FileName);
  7:   end;
  8: end;
  9: 

Here we call the Delphi function PromptForFileName, and if the user doesn’t cancel the operation, we call the document’s OpenFile method to load the text. For other commands, it gets a little more complex, and we start to see the advantages of separating the command logic out from the object that invokes it.

Other advantages are that it is easy to add new commands, because existing classes don’t need to change. This makes it a lower risk modification to your code. You can also use the Composite pattern to make larger macro commands out of several other commands.

Although our command implementations all make use of a receiver to ultimately carry out the operation, that’s not always necessary. The GoF example is that of a command to launch another application. The command may well have enough information to do that itself (by calling ShellExecute, say), without having to delegate the task to another object. It may also be necessary sometimes for the command to find its receiver dynamically. If our example application used MDI, then we might need to go and find the current document, for instance.

posted on 2011-07-01 12:31  Tony Liu  阅读(482)  评论(0编辑  收藏  举报

导航