📄 chap2.htm
字号:
<A NAME="auto1053"></A><P>All this leads us to the concept of <STRONG>transparent enclosure</STRONG>,which combines the notions of (1) single-child (orsingle-<STRONG>component</STRONG>) composition and (2) compatibleinterfaces. Clients generally can't tell whether they're dealing withthe component or its <STRONG>enclosure</STRONG> (i.e., the child's parent),especially if the enclosure simply delegates all its operations to itscomponent. But the enclosure can also <EM>augment</EM> the component'sbehavior by doing work of its own before and/or after delegating anoperation. The enclosure can also effectively add state to thecomponent. We'll see how next.</P><H3>Monoglyph</H3><A NAME="auto1054"></A><P>We can apply the concept of transparent enclosure to all glyphs thatembellish other glyphs. To make this concept concrete, we'll define asubclass of Glyph called <STRONG>MonoGlyph</STRONG> to serve as an abstractclass for "embellishment glyphs," likeBorder (see <A HREF="#editor_embellish-omt">Figure 2.7</A>).MonoGlyph stores a reference to a component and forwards all requests toit. That makes MonoGlyph totally transparent to clients by default.For example, MonoGlyph implements the <CODE>Draw</CODE> operation like this:</P><A NAME="auto1055"></A><PRE> void MonoGlyph::Draw (Window* w) { _component->Draw(w); }</PRE><A NAME="editor_embellish-omt"></A><P ALIGN=CENTER><IMG SRC="Pictures/embel061.gif"><BR><BR>Figure 2.7: MonoGlyph class relationships</P><A NAME="auto1056"></A><P>MonoGlyph subclasses reimplement at least one of these forwardingoperations. <CODE>Border::Draw</CODE>, for instance, first invokes the parentclass operation <CODE>MonoGlyph::Draw</CODE> on the component to let thecomponent do its part—that is, draw everything but the border. Then<CODE>Border::Draw</CODE> draws the border by calling a privateoperation called <CODE>DrawBorder</CODE>, the details of which we'llomit:</P><A NAME="auto1057"></A><PRE> void Border::Draw (Window* w) { MonoGlyph::Draw(w); DrawBorder(w); }</PRE><A NAME="auto1058"></A><P>Notice how <CODE>Border::Draw</CODE> effectively <EM>extends</EM> the parentclass operation to draw the border. This is in contrast to merely<EM>replacing</EM> the parent class operation, which would omit the call to<CODE>MonoGlyph::Draw</CODE>.</P><A NAME="scroller"></A><P>Another MonoGlyph subclass appears in <A HREF="#editor_embellish-omt">Figure 2.7</A>.<STRONG>Scroller</STRONG> is a MonoGlyph that draws its component in differentlocations based on the positions of two scroll bars, which it adds asembellishments. When Scroller draws its component, it tells thegraphics system to clip to its bounds. Clipping parts of the componentthat are scrolled out of view keeps them from appearing on the screen.</P><A NAME="auto1059"></A><P>Now we have all the pieces we need to add a border and a scrollinginterface to Lexi's text editing area. We compose the existingComposition instance in a Scroller instance to add the scrollinginterface, and we compose that in a Border instance. The resultingobject structure appears in <A HREF="#editor_embellish">Figure 2.8</A>.</P><A NAME="editor_embellish"></A><A NAME="Fig-2.8"></A><P ALIGN=CENTER><IMG SRC="Pictures/embel060.gif"><BR><BR>Figure 2.8: Embellished object structure</P><A NAME="auto1060"></A><P>Note that we can reverse the order of composition, putting thebordered composition into the Scroller instance. In that case theborder would be scrolled along with the text, which may or may not bedesirable. The point is, transparent enclosure makes it easy toexperiment with different alternatives, and it keeps clients free ofembellishment code.</P><A NAME="auto1061"></A><P>Note also how the border composes one glyph, not two or more. This isunlike compositions we've defined so far, in which parent objects wereallowed to have arbitrarily many children. Here, putting a borderaround something implies that "something" is singular. We couldassign a meaning to embellishing more than one object at a time, butthen we'd have to mix many kinds of composition in with the notion ofembellishment: row embellishment, column embellishment, and so forth.That won't help us, since we already have classes to do those kinds ofcompositions. So it's better to use existing classes for compositionand add new classes to embellish the result. Keeping embellishmentindependent of other kinds of composition both simplifies theembellishment classes and reduces their number. It also keeps us fromreplicating existing composition functionality.</P><A NAME="dec-patt"></A><H3>Decorator Pattern</H3><A NAME="auto1062"></A><P>The <A HREF="pat4dfs.htm" TARGET="_mainDisplayFrame">Decorator (175)</A> pattern captures class and objectrelationships that support embellishment by transparent enclosure.The term "embellishment" actually has broader meaning than whatwe've considered here. In the Decorator pattern, embellishment refersto anything that adds responsibilities to an object. We can thinkfor example of embellishing an abstract syntax tree with semanticactions, a finite state automaton with new transitions, or a networkof persistent objects with attribute tags. Decorator generalizes theapproach we've used in Lexi to make it more widely applicable.<A NAME="sec2-5"></A><H2><A HREF="#sec2-6"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Supporting Multiple Window Systems"></A>Supporting Multiple Look-and-Feel Standards</H2><A NAME="auto1063"></A><P>Achieving portability across hardware and software platforms is amajor problem in system design. Retargeting Lexi to a newplatform shouldn't require a major overhaul, or it wouldn't be worthretargeting. We should make porting as easy as possible.</P><A NAME="auto1064"></A><P>One obstacle to portability is the diversity of look-and-feel standards,which are intended to enforce uniformity between applications. Thesestandards define guidelines for how applications appear and react to theuser. While existing standards aren't that different from each other,people certainly won't confuse one for the other—Motif applications don'tlook and feel exactly like their counterparts on other platforms, and viceversa. An application that runs on more than one platform must conform tothe user interface style guide on each platform.</P><A NAME="auto1065"></A><P>Our design goals are to make Lexi conform to multiple existinglook-and-feel standards and to make it easy to add support for newstandards as they (invariably) emerge. We also want our design tosupport the ultimate in flexibility: changing Lexi's look and feelat run-time.</P><A NAME="widget"></A><H3>Abstracting Object Creation</H3><A NAME="auto1066"></A><P>Everything we see and interact with in Lexi's user interface is aglyph composed in other, invisible glyphs like Row and Column. Theinvisible glyphs compose visible ones like Button and Character and laythem out properly. Style guides have much to say about the look andfeel of so-called "widgets," another term for visible glyphs likebuttons, scroll bars, and menus that act as controlling elements in auser interface. Widgets might use simpler glyphs such as characters,circles, rectangles, and polygons to present data.</P><A NAME="auto1067"></A><P>We'll assume we have two sets of widget glyph classes with which toimplement multiple look-and-feel standards:</P><OL><A NAME="auto1068"></A><LI>A set of abstract Glyph subclasses for each category of widgetglyph. For example, an abstract class ScrollBar will augment the basicglyph interface to add general scrolling operations; Button is anabstract class that adds button-oriented operations; and so on.</LI><A NAME="auto1069"></A><P></P><A NAME="present-manage"></A><LI>A set of concrete subclasses for each abstract subclass thatimplement different look-and-feel standards. For example, ScrollBarmight have MotifScrollBar and PMScrollBar subclasses that implementMotif and Presentation Manager-style scroll bars, respectively.</LI></OL><A NAME="auto1070"></A><P>Lexi must distinguish between widget glyphs for different look-and-feelstyles. For example, when Lexi needs to put a button in its interface,it must instantiate a Glyph subclass for the right style of button(MotifButton, PMButton, MacButton, etc.).</P><A NAME="macintosh1"></A><P>It's clear that Lexi's implementation can't do this directly, say,using a constructor call in C++. That would hard-code the button of aparticular style, making it impossible to select the style atrun-time. We'd also have to track down and change every suchconstructor call to port Lexi to another platform. And buttons areonly one of a variety of widgets in Lexi's user interface.Littering our code with constructor calls to specific look-and-feelclasses yields a maintenance nightmare—miss just one, and you couldend up with a Motif menu in the middle of your Mac application.</P><A NAME="auto1071"></A><P>Lexi needs a way to determine the look-and-feel standard that's beingtargeted in order to create the appropriate widgets. Not only must weavoid making explicit constructor calls; we must also be able toreplace an entire widget set easily. We can achieve both by <EM>abstracting the process of object creation</EM>. An example willillustrate what we mean.</P><H3>Factories and Product Classes</H3><A NAME="auto1072"></A><P>Normally we might create an instance of a Motif scroll bar glyph with thefollowing C++ code:</P><A NAME="auto1073"></A><PRE> ScrollBar* sb = new MotifScrollBar;</PRE><A NAME="auto1074"></A><P>This is the kind of code to avoid if you want to minimizeLexi's look-and-feel dependencies. But suppose weinitialize <CODE>sb</CODE> as follows:</P><A NAME="auto1075"></A><PRE> ScrollBar* sb = guiFactory->CreateScrollBar();</PRE><A NAME="auto1076"></A><P>where <CODE>guiFactory</CODE> is an instance of a<STRONG>MotifFactory</STRONG> class. <CODE>CreateScrollBar</CODE>returns a new instance of the proper ScrollBar subclass for thelook and feel desired, Motif in this case. As far as clients areconcerned, the effect is the same as calling the MotifScrollBarconstructor directly. But there's a crucial difference: There'sno longer anything in the code that mentions Motif by name. The<CODE>guiFactory</CODE> object abstracts the process of creatingnot just Motif scroll bars but scroll bars for <EM>any</EM>look-and-feel standard. And <CODE>guiFactory</CODE> isn't limitedto producing scroll bars. It can manufacture a full range of widgetglyphs, including scroll bars, buttons, entry fields, menus, andso forth.</P><A NAME="auto1077"></A><P>All this is possible because MotifFactory is a subclass of<STRONG>GUIFactory</STRONG>, an abstract class that defines ageneral interface for creating widget glyphs. It includes operationslike <CODE>CreateScrollBar</CODE> and <CODE>CreateButton</CODE>for instantiating different kinds of widget glyphs. Subclasses ofGUIFactory implement these operations to return glyphs such asMotifScrollBar and PMButton that implement a particular look andfeel. <A HREF="#editor_factory_hierarchy">Figure 2.9</A> showsthe resulting class hierarchy for <CODE>guiFactory</CODE> objects.</P><A NAME="editor_factory_hierarchy"></A><P ALIGN=CENTER><IMG SRC="Pictures/facto056.gif"><BR><BR>Figure 2.9: GUIFactory class hierarchy</P><A NAME="productobjects"></A><P>We say that factories create <STRONG>product</STRONG> objects.Moreover, the products that a factory produces are related to oneanother; in this case, the products are all widgets for the samelook and feel. <A HREF="#editor_products">Figure 2.10</A>shows some of the product classes needed to make factories workfor widget glyphs.</P><A NAME="editor_products"></A><P ALIGN=CENTER><IMG SRC="Pictures/produ020.gif"><BR><BR>Figure 2.10: Abstract product classes and concrete subclasses</P><A NAME="auto1078"></A><P>The last question we have to answer is, Where does the <CODE>GUIFactory</CODE>instance come from? The answer is, Anywhere that's convenient. Thevariable <CODE>guiFactory</CODE> could be a global, a static member of awell-known class, or even a local variable if the entire user interface iscreated within one class or function. There's even a design pattern,<A HREF="pat3efs.htm" TARGET="_mainDisplayFrame">Singleton (127)</A>, for managing well-known, one-of-a-kindobjects like this. The important thing, though, is to initialize<CODE>guiFactory</CODE> at a point in the program <EM>before</EM> it's ever usedto create widgets but <EM>after</EM> it's clear which look and feel isdesired.</P><A NAME="auto1079"></A><P>If the look and feel is known at compile-time, then <CODE>guiFactory</CODE>can be initialized with a simple assignment of a new factory instanceat the beginning of the program:</P><A NAME="auto1080"></A><PRE> GUIFactory* guiFactory = new MotifFactory;</PRE><A NAME="auto1081"></A><P>If the user can specify the look and feel with a string name atstartup time, then the code to create the factory might be</P><A NAME="auto1082"></A><PRE> GUIFactory* guiFactory; const char* styleName = getenv("LOOK_AND_FEEL"); // user or environment supplies this at startup if (strcmp(styleName, "Motif") == 0) { guiFactory = new MotifFactory; } else if (strcmp(styleName, "Presentation_Manager") == 0) { guiFactory = new PMFactory; } else { guiFactory = new DefaultGUIFactory; }</PRE><A NAME="auto1083"></A>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -