📄 pat4a.htm
字号:
TreeDisplay, the adaptee is any hierarchical structure. A minimalist
interface might include two operations, one that defines how to
present a node in the hierarchical structure graphically, and another
that 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 Adaptee
interface in the TreeDisplay class. Subclasses must implement the
abstract operations and adapt the hierarchically structured object.
For example, a DirectoryTreeDisplay subclass will implement these
operations 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 can
display 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 hierarchical
structure to a <STRONG>delegate</STRONG> object. TreeDisplay can use a
different adaptation strategy by substituting a different delegate.
<A NAME="direc-browse"></A>
<P>For example, suppose there exists a DirectoryBrowser that uses a
TreeDisplay. DirectoryBrowser might make a good delegate for
adapting TreeDisplay to the hierarchical directory structure. In
dynamically typed languages like Smalltalk or Objective C, this
approach only requires an interface for registering the delegate with
the adapter. Then TreeDisplay simply forwards the requests to the
delegate. NEXTSTEP [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=NeXT_AppKit" TARGET="_mainDisplayFrame">Add94</A>] uses this approach heavily to
reduce subclassing.</P>
<A NAME="treeaccdeleg"></A>
<P>Statically typed languages like C++ require an explicit interface
definition for the delegate. We can specify such an interface by
putting the narrow interface that TreeDisplay requires into an
abstract TreeAccessorDelegate class. Then we can mix this interface
into the delegate of our choice—DirectoryBrowser in this
case—using inheritance. We use single inheritance if the
DirectoryBrowser has no existing parent class, multiple inheritance if
it does. Mixing classes together like this is easier than introducing
a new TreeDisplay subclass and implementing its operations
individually.</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 to
parameterize an adapter with one or more blocks. The block construct
supports adaptation without subclassing. A block can adapt a request,
and the adapter can store a block for each individual request. In our
example, this means TreeDisplay stores one block for converting a node
into 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 approach
offers a convenient alternative to subclassing.</P>
</LI>
</OL>
</OL>
<A NAME="samplecode"><A>
<H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0></A> Sample Code</H2>
<A NAME="auto1055"></A>
<P>We'll give a brief sketch of the implementation of class and object
adapters 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 opposing
corners. 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 shape
when the user manipulates it.<A NAME="fn1"></A><A
HREF="#footnote1"><SUP>1</SUP></A> <CODE>TextView</CODE> has no
equivalent operation. The class <CODE>TextShape</CODE> is an
adapter between these different interfaces.</P>
<A NAME="auto1057"></A>
<P>A class adapter uses multiple inheritance to adapt interfaces. The key
to class adapters is to use one inheritance branch to inherit the
interface and another branch to inherit the implementation. The usual
way to make this distinction in C++ is to inherit the interface
publicly and inherit the implementation privately. We'll use this
convention 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>'s
interface 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 of
requests 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 supported
by <CODE>TextView</CODE>) from scratch. Assume we've already implemented
a <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 with
different 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. It
must also call operations on its <CODE>TextView</CODE> object whenever
its own operations are called. In this example, assume that the client
creates 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 change
from the class adapter version, since it's implemented from scratch
and 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 adapter
requires a little more effort to write, but it's more flexible. For
example, the object adapter version of <CODE>TextShape</CODE> will work
equally well with subclasses of <CODE>TextView</CODE>---the client simply
passes 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></A> Known Uses</H2>
<A NAME="auto1070"></A>
<P>The Motivation example comes from ET++Draw, a drawing application
based on ET++ [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=et++" TARGET="_mainDisplayFrame">WGM88</A>]. ET++Draw
reuses the ET++ classes for text editing by using a TextShape
adapter class.</P>
<A NAME="auto1071"></A>
<P>InterViews 2.6 defines an Interactor abstract class for user
interface elements such as scroll bars, buttons, and menus [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=interviews_graphic" TARGET="_mainDisplayFrame">VL88</A>]. It also defines a
Graphic abstract class for structured graphic objects such as lines,
circles, polygons, and splines. Both Interactors and Graphics have
graphical appearances, but they have different interfaces and
implementations (they share no common parent class) and are therefore
incompatible—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 called
GraphicBlock, a subclass of Interactor that contains a Graphic
instance. The GraphicBlock adapts the interface of the Graphic
class to that of Interactor. The GraphicBlock lets a Graphic
instance be displayed, scrolled, and zoomed within an Interactor
structure.</P>
<A NAME="plugap-imp2"></A>
<P>Pluggable adapters are common in
ObjectWorks\Smalltalk [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=parcplace_smalltalk" TARGET="_mainDisplayFrame">Par90</A>]. Standard Smalltalk defines
a ValueModel class for views that display a single value. ValueModel
defines a <CODE>value</CODE>, <CODE>value:</CODE> interface for accessing
the value. These are abstract methods. Application writers access the
value with more domain-specific names like <CODE>width</CODE> and
<CODE>width:</CODE>, but they shouldn't have to subclass ValueModel
to adapt such application-specific names to the ValueModel interface.</P>
<A NAME="auto1073"></A>
<P>Instead, ObjectWorks\Smalltalk includes a subclass of ValueModel
called PluggableAdaptor. A PluggableAdaptor object adapts other
objects to the ValueModel interface (<CODE>value</CODE>,
<CODE>value:</CODE>). It can be parameterized with blocks for getting
and setting the desired value. PluggableAdaptor uses these blocks
internally to implement the
<CODE>value</CODE>, <CODE>value:</CODE> interface. PluggableAdaptor also
lets 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 blocks
automatically.</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 TableAdaptor
class. A TableAdaptor can adapt a sequence of objects to a tabular
presentation. The table displays one object per row. The client
parameterizes TableAdaptor with the set of messages that a table can
use to get the column values from an object.</P>
<A NAME="auto1075"></A>
<P>Some classes in NeXT's AppKit [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=NeXT_AppKit" TARGET="_mainDisplayFrame">Add94</A>] use delegate objects
to perform interface adaptation. An example is the NXBrowser class that
can display hierarchical lists of data. NXBrowser uses a delegate
object for accessing and adapting the data.</P>
<A NAME="marriage"></A>
<P>Meyer's "Marriage of Convenience" [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=meyer_book-88" TARGET="_mainDisplayFrame">Mey88</A>] is a form of
class adapter. Meyer describes how a FixedStack class adapts the
implementation 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></A> Related Patterns</H2>
<A NAME="auto1076"></A>
<P><A HREF="pat4bfs.htm" TARGET="_mainDisplayFrame">Bridge (151)</A> has
a structure similar to an object adapter, but Bridge has a different
intent: It is meant to separate an interface from its implementation
so that they can be varied easily and independently. An adapter
is 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 decorator
is thus more transparent to the application than an adapter is. As
a consequence, Decorator supports recursive composition, which
isn't possible with pure adapters.</P>
<A NAME="auto1078"></A>
<P><A HREF="pat4gfs.htm" TARGET="_mainDisplayFrame">Proxy (207)</A> defines
a representative or surrogate for another object and does not change
its 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>.
<A HREF="#fn1"><IMG SRC="gifsb/up3.gif" BORDER=0></A></P>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -