📄 pat5a.htm
字号:
forward only the fixed set of requests that the Handler class defines.</P><A NAME="auto1049"></A><P>An alternative is to use a single handler function that takes arequest code (e.g., an integer constant or a string) as parameter.This supports an open-ended set of requests. The only requirement isthat the sender and receiver agree on how the request should beencoded.</P><A NAME="auto1050"></A><P>This approach is more flexible, but it requires conditional statementsfor dispatching the request based on its code. Moreover, there's notype-safe way to pass parameters, so they must be packed and unpackedmanually. Obviously this is less safe than invoking an operationdirectly.</P><A NAME="auto1051"></A><P>To address the parameter-passing problem, we can use separate request<EM>objects</EM> that bundle request parameters. A <CODE>Request</CODE>class can represent requests explicitly, and new kinds of requests canbe defined by subclassing. Subclasses can define different parameters.Handlers must know the kind of request (that is, which<CODE>Request</CODE> subclass they're using) to access these parameters.</P><A NAME="typecheck-runtime"></A><P>To identify the request, <CODE>Request</CODE> can define an accessorfunction that returns an identifier for the class. Alternatively, thereceiver can use run-time type information if the implementationlanguages supports it.</P><A NAME="auto1052"></A><P>Here is a sketch of a dispatch function that uses request objects toidentify requests.A <CODE>GetKind</CODE> operation defined in the base <CODE>Request</CODE>class identifies the kind of request:</P><A NAME="auto1053"></A><PRE> void Handler::HandleRequest (Request* theRequest) { switch (theRequest->GetKind()) { case Help: // cast argument to appropriate type HandleHelp((HelpRequest*) theRequest); break; case Print: HandlePrint((PrintRequest*) theRequest); // ... break; default: // ... break; } }</PRE><A NAME="extnd-hndlr"></A><P>Subclasses can extend the dispatch by overriding<CODE>HandleRequest</CODE>. The subclass handles only therequests in which it's interested; other requests are forwarded to theparent class. In this way, subclasses effectively extend (rather thanoverride) the <CODE>HandleRequest</CODE> operation.For example, here's how an <CODE>ExtendedHandler</CODE> subclass extends<CODE>Handler</CODE>'s version of <CODE>HandleRequest</CODE>:</P><A NAME="auto1054"></A><PRE> class ExtendedHandler : public Handler { public: virtual void HandleRequest(Request* theRequest); // ... }; void ExtendedHandler::HandleRequest (Request* theRequest) { switch (theRequest->GetKind()) { case Preview: // handle the Preview request break; default: // let Handler handle other requests Handler::HandleRequest(theRequest); } }</PRE><A NAME="auto1055"></A><P></P><A NAME="doesnotunder"></A><A NAME="forward-req"></A><LI><EM>Automatic forwarding in Smalltalk.</EM>You can use the <CODE>doesNotUnderstand</CODE> mechanism in Smalltalk toforward requests. Messages that have no corresponding methods aretrapped in the implementation of <CODE>doesNotUnderstand</CODE>, whichcan be overridden to forward the message to an object's successor.Thus it isn't necessary to implement forwarding manually; the classhandles only the request in which it's interested, and it relies on<CODE>doesNotUnderstand</CODE> to forward all others.</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="auto1056"></A><P>The following example illustrates how a chain of responsibility canhandle requests for an on-line help system like the one describedearlier. The help request is an explicit operation. We'll use existingparent references in the widget hierarchy to propagate requestsbetween widgets in the chain, and we'll define a reference in theHandler class to propagate help requests between nonwidgets in thechain.</P><A NAME="auto1057"></A><P>The <CODE>HelpHandler</CODE> class defines the interface for handlinghelp requests. It maintains a help topic (which is empty by default)and keeps a reference to its successor on the chain of help handlers.The key operation is <CODE>HandleHelp</CODE>, which subclassesoverride. <CODE>HasHelp</CODE> is a convenience operation for checkingwhether there is an associated help topic.</P><A NAME="auto1058"></A><PRE> typedef int Topic; const Topic NO_HELP_TOPIC = -1; class HelpHandler { public: HelpHandler(HelpHandler* = 0, Topic = NO_HELP_TOPIC); virtual bool HasHelp(); virtual void SetHandler(HelpHandler*, Topic); virtual void HandleHelp(); private: HelpHandler* _successor; Topic _topic; }; HelpHandler::HelpHandler ( HelpHandler* h, Topic t ) : _successor(h), _topic(t) { } bool HelpHandler::HasHelp () { return _topic != NO_HELP_TOPIC; } void HelpHandler::HandleHelp () { if (_successor != 0) { _successor->HandleHelp(); } }</PRE><A NAME="widget-class"></A><P>All widgets are subclasses of the <CODE>Widget</CODE> abstract class.<CODE>Widget</CODE> is a subclass of <CODE>HelpHandler</CODE>, since alluser interface elements can have help associated with them. (We couldhave used a mixin-based implementation just as well.)</P><A NAME="auto1059"></A><PRE> class Widget : public HelpHandler { protected: Widget(Widget* parent, Topic t = NO_HELP_TOPIC); private: Widget* _parent; }; Widget::Widget (Widget* w, Topic t) : HelpHandler(w, t) { _parent = w; }</PRE><A NAME="auto1060"></A><P>In our example, a button is the first handler on the chain. The<CODE>Button</CODE> class is a subclass of <CODE>Widget</CODE>.The <CODE>Button</CODE> constructor takes two parameters: a reference toits enclosing widget and the help topic.</P><A NAME="auto1061"></A><PRE> class Button : public Widget { public: Button(Widget* d, Topic t = NO_HELP_TOPIC); virtual void HandleHelp(); // Widget operations that Button overrides... };</PRE><A NAME="auto1062"></A><P><CODE>Button</CODE>'s version of <CODE>HandleHelp</CODE> first tests to see ifthere is a help topic for buttons. If the developer hasn't definedone, then the request gets forwarded to the successor using the<CODE>HandleHelp</CODE> operation in <CODE>HelpHandler</CODE>. If there<EM>is</EM> a help topic, then the button displays it, and the searchends.</P><A NAME="auto1063"></A><PRE> Button::Button (Widget* h, Topic t) : Widget(h, t) { } void Button::HandleHelp () { if (HasHelp()) { // offer help on the button } else { HelpHandler::HandleHelp(); } }</PRE><A NAME="auto1064"></A><P><CODE>Dialog</CODE> implements a similar scheme, except that itssuccessor is not a widget but <EM>any</EM> help handler. In ourapplication this successor will be an instance of <CODE>Application</CODE>.</P><A NAME="auto1065"></A><PRE> class Dialog : public Widget { public: Dialog(HelpHandler* h, Topic t = NO_HELP_TOPIC); virtual void HandleHelp(); // Widget operations that Dialog overrides... // ... }; Dialog::Dialog (HelpHandler* h, Topic t) : Widget(0) { SetHandler(h, t); } void Dialog::HandleHelp () { if (HasHelp()) { // offer help on the dialog } else { HelpHandler::HandleHelp(); } }</PRE><A NAME="app2"></A><P>At the end of the chain is an instance of <CODE>Application</CODE>. Theapplication is not a widget, so <CODE>Application</CODE> is subclasseddirectly from <CODE>HelpHandler</CODE>.When a help request propagates to this level, theapplication can supply information on the application in general, orit can offer a list of different help topics:</P><A NAME="auto1067"></A><PRE> class Application : public HelpHandler { public: Application(Topic t) : HelpHandler(0, t) { } virtual void HandleHelp(); // application-specific operations... }; void Application::HandleHelp () { // show a list of help topics }</PRE><A NAME="dialog-231"></A><P>The following code creates and connects these objects. Here thedialog concerns printing, and so the objects have printing-relatedtopics assigned.</P><A NAME="auto1068"></A><PRE> const Topic PRINT_TOPIC = 1; const Topic PAPER_ORIENTATION_TOPIC = 2; const Topic APPLICATION_TOPIC = 3; Application* application = new Application(APPLICATION_TOPIC); Dialog* dialog = new Dialog(application, PRINT_TOPIC); Button* button = new Button(dialog, PAPER_ORIENTATION_TOPIC);</PRE><A NAME="auto1069"></A><P>We can invoke the help request by calling <CODE>HandleHelp</CODE> on anyobject on the chain. To start the search at the button object, justcall <CODE>HandleHelp</CODE> on it:</P><A NAME="auto1070"></A><PRE> button->HandleHelp();</PRE><A NAME="auto1071"></A><P>In this case, the button will handle the request immediately. Notethat any <CODE>HelpHandler</CODE> class could be made the successor of<CODE>Dialog</CODE>. Moreover, its successor could be changeddynamically. So no matter where a dialog is used, you'll get theproper context-dependent help information for it.</P><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="responder"></A><P>Several class libraries use the Chain of Responsibility pattern tohandle user events. They use different names for the Handler class,but the idea is the same: When the user clicks the mouse or presses akey, an event gets generated and passed along the chain.MacApp [<A HREF="bibfs.htm#macapp" TARGET="_mainDisplayFrame">App89</A>] and ET++ [<A HREF="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>] call it "EventHandler,"Symantec's TCL library [<A HREF="bibfs.htm#think" TARGET="_mainDisplayFrame">Sym93b</A>] calls it "Bureaucrat," andNeXT's AppKit [<A HREF="bibfs.htm#NeXT_AppKit" TARGET="_mainDisplayFrame">Add94</A>] uses the name "Responder."</P><A NAME="unidraw-use-cor"></A><A NAME="unidraw-use-comm"></A><P>The Unidraw framework for graphical editors defines Command objectsthat encapsulate requests to Component and ComponentViewobjects [<A HREF="bibfs.htm#unidraw_framework" TARGET="_mainDisplayFrame">VL90</A>]. Commands are requests in the sensethat a component or component view may interpret a command to performan operation. This corresponds to the "requests as objects"approach described in Implementation. Components and component viewsmay be structured hierarchically. A component or a component view mayforward command interpretation to its parent, which may in turnforward it to its parent, and so on, thereby forming a chain ofresponsibility.</P><A NAME="auto1072"></A><P>ET++ uses Chain of Responsibility to handle graphical update. Agraphical object calls the InvalidateRect operation whenever it mustupdate a part of its appearance. A graphical object can't handleInvalidateRect by itself, because it doesn't know enough about itscontext. For example, a graphical object can be enclosed in objectslike Scrollers or Zoomers that transform its coordinate system. Thatmeans the object might be scrolled or zoomed so that it's partiallyout of view. Therefore the default implementation of InvalidateRectforwards the request to the enclosing container object. The lastobject in the forwarding chain is a Window instance. By the timeWindow receives the request, the invalidation rectangle is guaranteedto be transformed properly. The Window handles InvalidateRect bynotifying the window system interface and requesting an update.</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="auto1073"></A><P>Chain of Responsibility is often applied in conjunction with <AHREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A>. There,a component's parent can act as its successor.</P><A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat5bfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5bfs.htm" TARGET="_mainDisplayFrame">Command</A><BR><A HREF="chap5fs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif" ALIGN=TOP BORDER=0></A> <A HREF="chap5fs.htm" TARGET="_mainDisplayFrame">Behavioral Patterns</A></P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -