📄 pat4a.htm
字号:
is, the smallest subset of operations that lets us do the adaptation.A narrow interface consisting of only a couple of operations is easierto adapt than an interface with dozens of operations. ForTreeDisplay, the adaptee is any hierarchical structure. A minimalistinterface might include two operations, one that defines how topresent a node in the hierarchical structure graphically, and anotherthat retrieves the node's children.</P><A NAME="auto1048"></A><P>The narrow interface leads to three implementation approaches:</P><OL><A NAME="absops"></A><LI TYPE=a><EM>Using abstract operations.</EM>Define corresponding abstract operations for the narrow Adapteeinterface in the TreeDisplay class. Subclasses must implement theabstract operations and adapt the hierarchically structured object.For example, a DirectoryTreeDisplay subclass will implement theseoperations by accessing the directory structure.<A NAME="adapter-param1"></A><P ALIGN=CENTER><IMG SRC="Pictures/adapt103.gif"></P><A NAME="auto1049"></A><P>DirectoryTreeDisplay specializes the narrow interface so that it candisplay directory structures made up of FileSystemEntity objects.</P></LI><A NAME="auto1050"></A><P></P><A NAME="use-dele"></A><LI TYPE=a><EM>Using delegate objects.</EM>In this approach, TreeDisplay forwards requests for accessing the hierarchicalstructure to a <STRONG>delegate</STRONG> object. TreeDisplay can use adifferent adaptation strategy by substituting a different delegate.<A NAME="direc-browse"></A><P>For example, suppose there exists a DirectoryBrowser that uses aTreeDisplay. DirectoryBrowser might make a good delegate foradapting TreeDisplay to the hierarchical directory structure. Indynamically typed languages like Smalltalk or Objective C, thisapproach only requires an interface for registering the delegate withthe adapter. Then TreeDisplay simply forwards the requests to thedelegate. NEXTSTEP [<A HREF="bibfs.htm#NeXT_AppKit" TARGET="_mainDisplayFrame">Add94</A>] uses this approach heavily toreduce subclassing.</P><A NAME="treeaccdeleg"></A><P>Statically typed languages like C++ require an explicit interfacedefinition for the delegate. We can specify such an interface byputting the narrow interface that TreeDisplay requires into anabstract TreeAccessorDelegate class. Then we can mix this interfaceinto the delegate of our choice—DirectoryBrowser in thiscase—using inheritance. We use single inheritance if theDirectoryBrowser has no existing parent class, multiple inheritance ifit does. Mixing classes together like this is easier than introducinga new TreeDisplay subclass and implementing its operationsindividually.</P><A NAME="adapter-param2"></A><P ALIGN=CENTER><IMG SRC="Pictures/adapt102.gif"></P></LI><A NAME="auto1051"></A><P></P><A NAME="parameterized"></A><LI TYPE=a><EM>Parameterized adapters.</EM>The usual way to support pluggable adapters in Smalltalk is toparameterize an adapter with one or more blocks. The block constructsupports adaptation without subclassing. A block can adapt a request,and the adapter can store a block for each individual request. In ourexample, this means TreeDisplay stores one block for converting a nodeinto a GraphicNode and another block for accessing a node's children.<A NAME="auto1052"></A><P>For example, to create TreeDisplay on a directory hierarchy, we write</P><A NAME="auto1053"></A><PRE> directoryDisplay := (TreeDisplay on: treeRoot) getChildrenBlock: [:node | node getSubdirectories] createGraphicNodeBlock: [:node | node createGraphicNode].</PRE><A NAME="auto1054"></A><P>If you're building interface adaptation into a class, this approachoffers a convenient alternative to subclassing.</P></LI></OL></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="auto1055"></A><P>We'll give a brief sketch of the implementation of class and objectadapters for the Motivation example beginning with the classes<CODE>Shape</CODE> and <CODE>TextView</CODE>.</P><A NAME="auto1056"></A><PRE> class Shape { public: Shape(); virtual void BoundingBox( Point& bottomLeft, Point& topRight ) const; virtual Manipulator* CreateManipulator() const; }; class TextView { public: TextView(); void GetOrigin(Coord& x, Coord& y) const; void GetExtent(Coord& width, Coord& height) const; virtual bool IsEmpty() const; };</PRE><A NAME="textshape2"></A><P><CODE>Shape</CODE> assumes a bounding box defined by its opposingcorners. In contrast, <CODE>TextView</CODE> is defined by an origin,height, and width. <CODE>Shape</CODE> also defines a<CODE>CreateManipulator</CODE> operation for creating a<CODE>Manipulator</CODE> object, which knows how to animate a shapewhen the user manipulates it.<A NAME="fn1"></A><AHREF="#footnote1"><SUP>1</SUP></A> <CODE>TextView</CODE> has noequivalent operation. The class <CODE>TextShape</CODE> is anadapter between these different interfaces.</P><A NAME="auto1057"></A><P>A class adapter uses multiple inheritance to adapt interfaces. The keyto class adapters is to use one inheritance branch to inherit theinterface and another branch to inherit the implementation. The usualway to make this distinction in C++ is to inherit the interfacepublicly and inherit the implementation privately. We'll use thisconvention to define the <CODE>TextShape</CODE> adapter.</P><A NAME="auto1058"></A><PRE> class TextShape : public Shape, private TextView { public: TextShape(); virtual void BoundingBox( Point& bottomLeft, Point& topRight ) const; virtual bool IsEmpty() const; virtual Manipulator* CreateManipulator() const; };</PRE><A NAME="auto1059"></A><P>The <CODE>BoundingBox</CODE> operation converts <CODE>TextView</CODE>'sinterface to conform to <CODE>Shape</CODE>'s.</P><A NAME="auto1060"></A><PRE> void TextShape::BoundingBox ( Point& bottomLeft, Point& topRight ) const { Coord bottom, left, width, height; GetOrigin(bottom, left); GetExtent(width, height); bottomLeft = Point(bottom, left); topRight = Point(bottom + height, left + width); }</PRE><A NAME="auto1061"></A><P>The <CODE>IsEmpty</CODE> operation demonstrates the direct forwarding ofrequests common in adapter implementations:</P><A NAME="auto1062"></A><PRE> bool TextShape::IsEmpty () const { return TextView::IsEmpty(); }</PRE><A NAME="auto1063"></A><P>Finally, we define <CODE>CreateManipulator</CODE> (which isn't supportedby <CODE>TextView</CODE>) from scratch. Assume we've already implementeda <CODE>TextManipulator</CODE> class that supports manipulation of a<CODE>TextShape</CODE>.</P><A NAME="auto1064"></A><PRE> Manipulator* TextShape::CreateManipulator () const { return new TextManipulator(this); }</PRE><A NAME="auto1065"></A><P>The object adapter uses object composition to combine classes withdifferent interfaces. In this approach, the adapter<CODE>TextShape</CODE> maintains a pointer to<CODE>TextView</CODE>.</P><A NAME="auto1066"></A><PRE> class TextShape : public Shape { public: TextShape(TextView*); virtual void BoundingBox( Point& bottomLeft, Point& topRight ) const; virtual bool IsEmpty() const; virtual Manipulator* CreateManipulator() const; private: TextView* _text; };</PRE><A NAME="textshape3"></A><P><CODE>TextShape</CODE> must initialize the pointer to the<CODE>TextView</CODE> instance, and it does so in the constructor. Itmust also call operations on its <CODE>TextView</CODE> object wheneverits own operations are called. In this example, assume that the clientcreates the <CODE>TextView</CODE> object and passes it to the<CODE>TextShape</CODE> constructor:</P><A NAME="auto1067"></A><PRE> TextShape::TextShape (TextView* t) { _text = t; } void TextShape::BoundingBox ( Point& bottomLeft, Point& topRight ) const { Coord bottom, left, width, height; _text->GetOrigin(bottom, left); _text->GetExtent(width, height); bottomLeft = Point(bottom, left); topRight = Point(bottom + height, left + width); } bool TextShape::IsEmpty () const { return _text->IsEmpty(); }</PRE><A NAME="et-use-adapter"></A><P><CODE>CreateManipulator</CODE>'s implementation doesn't changefrom the class adapter version, since it's implemented from scratchand doesn't reuse any existing <CODE>TextView</CODE> functionality.</P><A NAME="auto1068"></A><PRE> Manipulator* TextShape::CreateManipulator () const { return new TextManipulator(this); }</PRE><A NAME="auto1069"></A><P>Compare this code to the class adapter case. The object adapterrequires a little more effort to write, but it's more flexible. Forexample, the object adapter version of <CODE>TextShape</CODE> will workequally well with subclasses of <CODE>TextView</CODE>—the client simplypasses an instance of a <CODE>TextView</CODE> subclass to the<CODE>TextShape</CODE> constructor.</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="auto1070"></A><P>The Motivation example comes from ET++Draw, a drawing applicationbased on ET++ [<A HREF="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>]. ET++Drawreuses the ET++ classes for text editing by using a TextShapeadapter class.</P><A NAME="auto1071"></A><P>InterViews 2.6 defines an Interactor abstract class for userinterface elements such as scroll bars, buttons, and menus [<A HREF="bibfs.htm#interviews_graphic" TARGET="_mainDisplayFrame">VL88</A>]. It also defines aGraphic abstract class for structured graphic objects such as lines,circles, polygons, and splines. Both Interactors and Graphics havegraphical appearances, but they have different interfaces andimplementations (they share no common parent class) and are thereforeincompatible—you can't embed a structured graphic object in,say, a dialog box directly.</P><A NAME="auto1072"></A><P>Instead, InterViews 2.6 defines an object adapter calledGraphicBlock, a subclass of Interactor that contains a Graphicinstance. The GraphicBlock adapts the interface of the Graphicclass to that of Interactor. The GraphicBlock lets a Graphicinstance be displayed, scrolled, and zoomed within an Interactorstructure.</P><A NAME="plugap-imp2"></A><P>Pluggable adapters are common inObjectWorks\Smalltalk [<A HREF="bibfs.htm#parcplace_smalltalk" TARGET="_mainDisplayFrame">Par90</A>]. Standard Smalltalk definesa ValueModel class for views that display a single value. ValueModeldefines a <CODE>value</CODE>, <CODE>value:</CODE> interface for accessingthe value. These are abstract methods. Application writers access thevalue with more domain-specific names like <CODE>width</CODE> and<CODE>width:</CODE>, but they shouldn't have to subclass ValueModelto adapt such application-specific names to the ValueModel interface.</P><A NAME="auto1073"></A><P>Instead, ObjectWorks\Smalltalk includes a subclass of ValueModelcalled PluggableAdaptor. A PluggableAdaptor object adapts otherobjects to the ValueModel interface (<CODE>value</CODE>,<CODE>value:</CODE>). It can be parameterized with blocks for gettingand setting the desired value. PluggableAdaptor uses these blocksinternally to implement the<CODE>value</CODE>, <CODE>value:</CODE> interface. PluggableAdaptor alsolets you pass in the selector names (e.g., <CODE>width</CODE>,<CODE>width:</CODE>) directly for syntactic convenience.It converts these selectors into the corresponding blocksautomatically.</P><A NAME="plugap-149c"></A><P ALIGN=CENTER><IMG SRC="Pictures/plugg021.gif"></P><A NAME="auto1074"></A><P>Another example from ObjectWorks\Smalltalk is the TableAdaptorclass. A TableAdaptor can adapt a sequence of objects to a tabularpresentation. The table displays one object per row. The clientparameterizes TableAdaptor with the set of messages that a table canuse to get the column values from an object.</P><A NAME="auto1075"></A><P>Some classes in NeXT's AppKit [<A HREF="bibfs.htm#NeXT_AppKit" TARGET="_mainDisplayFrame">Add94</A>] use delegate objectsto perform interface adaptation. An example is the NXBrowser class thatcan display hierarchical lists of data. NXBrowser uses a delegateobject for accessing and adapting the data.</P><A NAME="marriage"></A><P>Meyer's "Marriage of Convenience" [<A HREF="bibfs.htm#meyer_book-88" TARGET="_mainDisplayFrame">Mey88</A>] is a form ofclass adapter. Meyer describes how a FixedStack class adapts theimplementation of an Array class to the interface of a Stack class.The result is a stack containing a fixed number of entries.</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="auto1076"></A><P><A HREF="pat4bfs.htm" TARGET="_mainDisplayFrame">Bridge (151)</A> hasa structure similar to an object adapter, but Bridge has a differentintent: It is meant to separate an interface from its implementationso that they can be varied easily and independently. An adapteris meant to change the interface of an <EM>existing</EM> object.</P><A NAME="auto1077"></A><P><A HREF="pat4dfs.htm" TARGET="_mainDisplayFrame">Decorator (175)</A>enhances another object without changing its interface. A decoratoris thus more transparent to the application than an adapter is. Asa consequence, Decorator supports recursive composition, whichisn't possible with pure adapters.</P><A NAME="auto1078"></A><P><A HREF="pat4gfs.htm" TARGET="_mainDisplayFrame">Proxy (207)</A> definesa representative or surrogate for another object and does not changeits interface.</P><A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat4bfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat4bfs.htm" TARGET="_mainDisplayFrame">Bridge</A><BR><A HREF="chap4fs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif" ALIGN=TOP BORDER=0></A> <A HREF="chap4fs.htm" TARGET="_mainDisplayFrame">Structural Patterns</A></P><HR><A NAME="footnote1"></A><P><SUP>1</SUP><CODE>CreateManipulator</CODE> is an example of a<A HREF="pat3cfs.htm" TARGET="_mainDisplayFrame">Factory Method (107)</A>.</P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -