?? pat5b.htm
字號:
any other object. Somewhere in between these extremes are commandsthat have enough knowledge to find their receiver dynamically.</LI><A NAME="auto1052"></A><P></P><A NAME="supp-undo-redo"></A><LI><EM>Supporting undo and redo.</EM>Commands can support undo and redo capabilities if they provide a wayto reverse their execution (e.g., an Unexecute or Undo operation). AConcreteCommand class might need to store additional state to do so. Thisstate can include</LI><A NAME="auto1053"></A><P></P><UL><A NAME="receive-part-command2"></A><LI>the Receiver object, which actually carries out operations inresponse to the request,</LI><A NAME="auto1054"></A><P></P><A NAME="auto1055"></A><LI>the arguments to the operation performed on the receiver, and</LI><A NAME="auto1056"></A><P></P><A NAME="auto1057"></A><LI>any original values in the receiver that can changeas a result of handling the request. The receiver must provideoperations that let the command return the receiver to its prior state.</LI></UL><A NAME="auto1058"></A><P></P><A NAME="auto1059"></A><P>To support one level of undo, an application needs to store only thecommand that was executed last. For multiple-level undo and redo, theapplication needs a <STRONG>history list</STRONG> of commands that havebeen executed, where the maximum length of the list determines thenumber of undo/redo levels. The history list stores sequences ofcommands that have been executed. Traversing backward through thelist and reverse-executing commands cancels their effect; traversingforward and executing commands reexecutes them.</P><A NAME="auto1060"></A><P>An undoable command might have to be copied before it can be placed onthe history list. That's because the command object that carried outthe original request, say, from a MenuItem, will perform otherrequests at later times. Copying is required to distinguish differentinvocations of the same command if its state can vary acrossinvocations.</P><A NAME="proto-w-command"></A><P>For example, a DeleteCommand that deletes selected objects must storedifferent sets of objects each time it's executed. Therefore theDeleteCommand object must be copied following execution, and the copyis placed on the history list. If the command's state never changeson execution, then copying is not required—only a reference to thecommand need be placed on the history list. Commands that must becopied before being placed on the history list act as prototypes (see<A HREF="pat3dfs.htm" TARGET="_mainDisplayFrame">Prototype (117)</A>).</P><A NAME="avoid-error"></A><LI><EM>Avoiding error accumulation in the undo process.</EM>Hysteresis can be a problem in ensuring a reliable,semantics-preserving undo/redo mechanism. Errors can accumulate ascommands are executed, unexecuted, and reexecuted repeatedly so thatan application's state eventually diverges from original values. Itmay be necessary therefore to store more information in the command toensure that objects are restored to their original state. The<A HREF="pat5ffs.htm" TARGET="_mainDisplayFrame">Memento (283)</A> pattern can be applied to give the commandaccess to this information without exposing the internals of otherobjects.</LI><A NAME="auto1061"></A><P></P><A NAME="template-implement"></A><LI><EM>Using C++ templates.</EM>For commands that (1) aren't undoable and (2) don't require arguments,we can use C++ templates to avoid creating a Command subclass forevery kind of action and receiver. We show how to do this in the SampleCode section.</LI></OL><A NAME="samplecode"><A><H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Known Uses"></A> Sample Code</H2> <A NAME="auto1062"></A><P>The C++ code shown here sketches the implementation of the Command classesin the Motivation section. We'll define <CODE>OpenCommand</CODE>,<CODE>PasteCommand</CODE>, and <CODE>MacroCommand</CODE>. First theabstract <CODE>Command</CODE> class:</P><A NAME="auto1063"></A><PRE> class Command { public: virtual ~Command(); virtual void Execute() = 0; protected: Command(); };</PRE><A NAME="auto1064"></A><P><CODE>OpenCommand</CODE> opens a document whose name is supplied by theuser. An <CODE>OpenCommand</CODE> must be passed an<CODE>Application</CODE> object in its constructor. <CODE>AskUser</CODE> is animplementation routine that prompts the user for the name of thedocument to open.</P><A NAME="auto1065"></A><PRE> class OpenCommand : public Command { public: OpenCommand(Application*); virtual void Execute(); protected: virtual const char* AskUser(); private: Application* _application; char* _response; }; OpenCommand::OpenCommand (Application* a) { _application = a; } void OpenCommand::Execute () { const char* name = AskUser(); if (name != 0) { Document* document = new Document(name); _application->Add(document); document->Open(); } }</PRE><A NAME="auto1066"></A><P>A <CODE>PasteCommand</CODE> must be passed a <CODE>Document</CODE> object asits receiver. The receiver is given as a parameter to <CODE>PasteCommand</CODE>'sconstructor.</P><A NAME="auto1067"></A><PRE> class PasteCommand : public Command { public: PasteCommand(Document*); virtual void Execute(); private: Document* _document; }; PasteCommand::PasteCommand (Document* doc) { _document = doc; } void PasteCommand::Execute () { _document->Paste(); }</PRE><A NAME="auto1068"></A><P>For simple commands that aren't undoable and don't require arguments,we can use a class template to parameterize the command's receiver.We'll define a template subclass <CODE>SimpleCommand</CODE> for suchcommands. <CODE>SimpleCommand</CODE> is parameterized by the<CODE>Receiver</CODE> type and maintains a binding between a receiver objectand an action stored as a pointer to a member function.</P><A NAME="auto1069"></A><PRE> template <class Receiver> class SimpleCommand : public Command { public: typedef void (Receiver::* Action)(); SimpleCommand(Receiver* r, Action a) : _receiver(r), _action(a) { } virtual void Execute(); private: Action _action; Receiver* _receiver; };</PRE><A NAME="auto1070"></A><P>The constructor stores the receiver and the action in the correspondinginstance variables. <CODE>Execute</CODE> simply applies the action to thereceiver.</P><A NAME="auto1071"></A><PRE> template <class Receiver> void SimpleCommand<Receiver>::Execute () { (_receiver->*_action)(); }</PRE><A NAME="auto1072"></A><P>To create a command that calls <CODE>Action</CODE>on an instance of class <CODE>MyClass</CODE>, a client simply writes</P><A NAME="auto1073"></A><PRE> MyClass* receiver = new MyClass; // ... Command* aCommand = new SimpleCommand<MyClass>(receiver, &MyClass::Action); // ... aCommand->Execute();</PRE><A NAME="auto1074"></A><P>Keep in mind that this solution only works for simple commands. Morecomplex commands that keep track of not only their receivers but alsoarguments and/or undo state require a <CODE>Command</CODE> subclass.</P><A NAME="macrocommand2"></A><P>A <CODE>MacroCommand</CODE> manages a sequence of subcommands and providesoperations for adding and removing subcommands. No explicit receiveris required, because the subcommands already define their receiver.</P><A NAME="auto1075"></A><PRE> class MacroCommand : public Command { public: MacroCommand(); virtual ~MacroCommand(); virtual void Add(Command*); virtual void Remove(Command*); virtual void Execute(); private: List<Command*>* _cmds; };</PRE><A NAME="auto1076"></A><P>The key to the <CODE>MacroCommand</CODE> is its <CODE>Execute</CODE> memberfunction. This traverses all the subcommands and performs<CODE>Execute</CODE> on each of them.</P><A NAME="auto1077"></A><PRE> void MacroCommand::Execute () { ListIterator<Command*> i(_cmds); for (i.First(); !i.IsDone(); i.Next()) { Command* c = i.CurrentItem(); c->Execute(); } }</PRE><A NAME="auto1078"></A><P>Note that should the <CODE>MacroCommand</CODE> implement an<CODE>Unexecute</CODE> operation, then its subcommands must beunexecuted in <EM>reverse</EM> order relative to <CODE>Execute</CODE>'simplementation.</P><A NAME="auto1079"></A><P>Finally, <CODE>MacroCommand</CODE> must provide operations to manage itssubcommands. The <CODE>MacroCommand</CODE> is also responsible fordeleting its subcommands.</P><A NAME="auto1080"></A><PRE> void MacroCommand::Add (Command* c) { _cmds->Append(c); } void MacroCommand::Remove (Command* c) { _cmds->Remove(c); }</PRE><A NAME="knownuses"><A><H2><A HREF="#relatedpatterns"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Related Patterns"></A> Known Uses</H2> <A NAME="unidraw-use-comm"></A><P>Perhaps the first example of the Command pattern appears in a paper byLieberman [<A HREF="bibfs.htm#lieberman_menus" TARGET="_mainDisplayFrame">Lie85</A>]. MacApp [<A HREF="bibfs.htm#macapp" TARGET="_mainDisplayFrame">App89</A>] popularizedthe notion of commands for implementing undoable operations.ET++ [<A HREF="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>], InterViews [<A HREF="bibfs.htm#InterViews3.1" TARGET="_mainDisplayFrame">LCI+92</A>], andUnidraw [<A HREF="bibfs.htm#unidraw_framework" TARGET="_mainDisplayFrame">VL90</A>] also define classes that follow theCommand pattern. InterViews defines an Action abstract class thatprovides command functionality. It also defines an ActionCallbacktemplate, parameterized by action method, that can instantiate commandsubclasses automatically.</P><A NAME="think-use-comm"></A><P>The THINK class library [<A HREF="bibfs.htm#think" TARGET="_mainDisplayFrame">Sym93b</A>] also uses commands to supportundoable actions. Commands in THINK are called "Tasks." Taskobjects are passed along a <A HREF="pat5afs.htm" TARGET="_mainDisplayFrame">Chain of Responsibility (223)</A>for consumption.</P><A NAME="auto1081"></A><P>Unidraw's command objects are unique in that they can behave likemessages. A Unidraw command may be sent to another object forinterpretation, and the result of the interpration varies with thereceiving object. Moreover, the receiver may delegate theinterpretation to another object, typically the receiver's parent in alarger structure as in a Chain of Responsibility. The receiver of aUnidraw command is thus computed rather than stored. Unidraw'sinterpretation mechanism depends on run-time type information.</P><A NAME="functor"></A><P>Coplien describes how to implement <STRONG>functors</STRONG>, objects thatare functions, in C++ [<A HREF="bibfs.htm#coplien_idioms" TARGET="_mainDisplayFrame">Cop92</A>]. He achieves a degree oftransparency in their use by overloading the function call operator(<CODE>operator()</CODE>). The Command pattern is different; its focusis on maintaining a <EM>binding between</EM> a receiver and a function(i.e., action), not just maintaining a function.</P><A NAME="relatedpatterns"></A><H2><A HREF="#last"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: navigation"></A> Related Patterns</H2> <A NAME="auto1082"></A><P>A <A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A>can be used to implement MacroCommands.</P><A NAME="auto1083"></A><P>A <A HREF="pat5ffs.htm" TARGET="_mainDisplayFrame">Memento (283)</A>can keep state the command requires to undo its effect.</P><A NAME="auto1084"></A><P>A command that must be copied before being placed on the historylist acts as a <A HREF="pat3dfs.htm"TARGET="_mainDisplayFrame">Prototype (117)</A>.</P><A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat5cfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5cfs.htm" TARGET="_mainDisplayFrame">Interpreter</A><BR><A HREF="pat5afs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5afs.htm" TARGET="_mainDisplayFrame">Chain of Responsibility</A></P></BODY></HTML>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -