📄 chap2.htm
字号:
themselves, (2) what space they occupy, and (3) their children andparent.</P><A NAME="window"></A><P>Glyph subclasses redefine the <CODE>Draw</CODE> operation to renderthemselves onto a window. They are passed a reference to a <CODE>Window</CODE>object in the call to <CODE>Draw</CODE>. The <STRONG>Window</STRONG> class definesgraphics operations for rendering text and basic shapes in a window on thescreen. A <STRONG>Rectangle</STRONG> subclass of Glyph might redefine<CODE>Draw</CODE> as follows:</P><A NAME="auto1030"></A><PRE> void Rectangle::Draw (Window* w) { w->DrawRect(_x0, _y0, _x1, _y1); }</PRE><A NAME="auto1031"></A><P>where <CODE>_x0</CODE>, <CODE>_y0</CODE>, <CODE>_x1</CODE>, and <CODE>_y1</CODE>are data members of <CODE>Rectangle</CODE> that define two opposing corners ofthe rectangle. <CODE>DrawRect</CODE> is the Window operation that makesthe rectangle appear on the screen.<A NAME="auto1032"></A><P>A parent glyph often needs to know how much space a child glyph occupies,for example, to arrange it and other glyphs in a line so that none overlaps(as shown in <A HREF="#editor_object_structure">Figure 2.3</A>). The<CODE>Bounds</CODE> operation returns the rectangular area that the glyphoccupies. It returns the opposite corners of the smallest rectangle thatcontains the glyph. Glyph subclasses redefine this operation to return therectangular area in which they draw.</P><A NAME="auto1033"></A><P>The <CODE>Intersects</CODE> operation returns whether a specified pointintersects the glyph. Whenever the user clicks somewhere in thedocument, Lexi calls this operation to determine which glyph orglyph structure is under the mouse. The Rectangle class redefinesthis operation to compute the intersection of the rectangle and thegiven point.</P><A NAME="auto1034"></A><P>Because glyphs can have children, we need a common interface toadd, remove, and access those children. For example, a Row's childrenare the glyphs it arranges into a row. The <CODE>Insert</CODE>operation inserts a glyph at a position specified by an integerindex.<A NAME="fn5"></A><A HREF="#footnote5"><SUP>5</SUP></A> The <CODE>Remove</CODE>operation removes a specified glyph if it is indeed a child.</P><A NAME="auto1035"></A><P>The <CODE>Child</CODE> operation returns the child (if any) at the givenindex. Glyphs like Row that can have children should use <CODE>Child</CODE>internally instead of accessing the child data structure directly. That wayyou won't have to modify operations like <CODE>Draw</CODE> that iteratethrough the children when you change the data structure from, say, an arrayto a linked list. Similarly, <CODE>Parent</CODE> provides a standard interfaceto the glyph's parent, if any. Glyphs in Lexi store a reference totheir parent, and their <CODE>Parent</CODE> operation simply returns thisreference.</P><H3>Composite Pattern</H3><A NAME="auto1036"></A><P>Recursive composition is good for more than just documents. We can useit to represent any potentially complex, hierarchical structure. The<A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A> pattern captures the essence ofrecursive composition in object-oriented terms. Now would be a goodtime to turn to that pattern and study it, referring back to thisscenario as needed.</P><A NAME="sec2-3"></A><H2><A HREF="#sec2-4"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Embellishing the User Interface"></A>Formatting</H2><A NAME="editor_sec_formatting"></A><P>We've settled on a way to <EM>represent</EM> the document's physicalstructure. Next, we need to figure out how to construct a <EM>particular</EM> physical structure, one that corresponds to a properlyformatted document. Representation and formatting are distinct: Theability to capture the document's physical structure doesn't tell ushow to arrive at a particular structure. This responsibility restsmostly on Lexi. It must break text into lines, lines into columns,and so on, taking into account the user's higher-level desires. Forexample, the user might want to vary margin widths, indentation, andtabulation; single or double space; and probably many other formattingconstraints.<A NAME="fn6"></A><A HREF="#footnote6"><SUP>6</SUP></A>Lexi'sformatting algorithm must take all of these into account.</P><A NAME="auto1037"></A><P>By the way, we'll restrict "formatting" to mean breaking a collection ofglyphs into lines. In fact, we'll use the terms "formatting" and"linebreaking" interchangeably. The techniques we'll discuss applyequally well to breaking lines into columns and to breaking columns intopages.</P><H3>Encapsulating the Formatting Algorithm</H3><A NAME="auto1038"></A><P>The formatting process, with all its constraints and details, isn't easy toautomate. There are many approaches to the problem, and people have come upwith a variety of formatting algorithms with different strengths andweaknesses. Because Lexi is a WYSIWYG editor, an important trade-off toconsider is the balance between formatting quality and formatting speed. Wewant generally good response from the editor without sacrificing how goodthe document looks. This trade-off is subject to many factors, not all ofwhich can be ascertained at compile-time. For example, the user mighttolerate slightly slower response in exchange for better formatting. Thattrade-off might make an entirely different formatting algorithm moreappropriate than the current one. Another, more implementation-driventrade-off balances formatting speed and storage requirements: It may bepossible to decrease formatting time by caching more information.</P><A NAME="auto1039"></A><P>Because formatting algorithms tend to be complex, it's also desirableto keep them well-contained or—better yet—completely independentof the document structure. Ideally we could add a new kind of Glyphsubclass without regard to the formatting algorithm. Conversely,adding a new formatting algorithm shouldn't require modifying existingglyphs.</P><A NAME="auto1040"></A><P>These characteristics suggest we should design Lexi so that it'seasy to change the formatting algorithm at least at compile-time, ifnot at run-time as well. We can isolate the algorithm and make iteasily replaceable at the same time by encapsulating it in an object.More specifically, we'll define a separate class hierarchy for objectsthat encapsulate formatting algorithms. The root of the hierarchy willdefine an interface that supports a wide range of formattingalgorithms, and each subclass will implement the interface to carryout a particular algorithm. Then we can introduce a Glyph subclassthat will structure its children automatically using a given algorithmobject.</P><H3>Compositor and Composition</H3><A NAME="auto1041"></A><P>We'll define a <STRONG>Compositor</STRONG> class for objectsthat can encapsulate a formatting algorithm. The interface (<A HREF="#editor_basic_compositor_interface">Table 2.2</A>) letsthe compositor know <EM>what</EM> glyphs to format and <EM>when</EM>to do the formatting. The glyphs it formats are the children ofa special Glyph subclass called <STRONG>Composition</STRONG>. Acomposition gets an instance of a Compositor subclass (specializedfor a particular linebreaking algorithm) when it is created, andit tells the compositor to <CODE>Compose</CODE> its glyphs whennecessary, for example, when the user changes a document.<A HREF="#editor_composition_and_compositor_class_relationships">Figure 2.5</A>depicts the relationships between the Composition and Compositor classes.</P><A NAME="editor_basic_compositor_interface"></A><CENTER><TABLE CELLPADDING = 4 BORDER = 1 CELLSPACING = 0 BGCOLOR = #99CCFF><A NAME="auto1042"></A><TR><TH BGCOLOR=#6699CC ALIGN=CENTER>Responsibility</TH><TH BGCOLOR=#6699CC ALIGN=CENTER>Operations</TH></TR><A NAME="auto1043"></A><TR><TD>what to format</TD><TD><CODE>void SetComposition(Composition*)</CODE></TD></TR><A NAME="auto1044"></A><TR><TD>when to format</TD><TD><CODE>virtual void Compose()</CODE></TD></TR></TABLE></CENTER><P ALIGN=CENTER>Table 2.2 Basic compositor interface</P><A NAME="42c"></A><A NAME="editor_composition_and_compositor_class_relationships"></A><P ALIGN=CENTER><IMG SRC="Pictures/compo071.gif"><BR><BR>Figure 2.5: Composition and Compositor class relationships</P><A NAME="auto1045"></A><P>An unformatted Composition object contains only the visibleglyphs that make up the document's basic content. It doesn't containglyphs that determine the document's physical structure, such asRow and Column. The composition is in this state just after it'screated and initialized with the glyphs it should format. Whenthe composition needs formatting, it calls its compositor's<CODE>Compose</CODE> operation. The compositor in turn iteratesthrough the composition's children and inserts new Row and Columnglyphs according to its linebreaking algorithm.<A NAME="fn7"></A><AHREF="#footnote7"><SUP>7</SUP></A> <A HREF="#editor_compositor_object_structure">Figure 2.6</A> shows the resulting objectstructure. Glyphs that the compositor created and inserted intothe object structure appear with gray backgrounds in the figure.</P><A NAME="editor_compositor_object_structure"></A><A NAME="simple-compositor-42c"></A><P ALIGN=CENTER><IMG SRC="Pictures/compo070.gif"><BR><BR>Figure 2.6: Object structure reflectingcompositor-directed linebreaking</P><A NAME="document-color"></A><A NAME="simple-compositor"></A><A NAME="tex"></A><P>Each Compositor subclass can implement a different linebreaking algorithm.For example, a SimpleCompositor might do a quick pass without regard forsuch esoterica as the document's "color." Good color means having an evendistribution of text and whitespace. A TeXCompositor would implement thefull TeX algorithm [<A HREF="bibfs.htm#tex" TARGET="_mainDisplayFrame">Knu84</A>], which takes things like color into accountin exchange for longer formatting times.</P><A NAME="auto1046"></A><P>The Compositor-Composition class split ensures a strong separationbetween code that supports the document's physical structure and thecode for different formatting algorithms. We can add new Compositorsubclasses without touching the glyph classes, and vice versa. Infact, we can change the linebreaking algorithm at run-time by adding asingle <CODE>SetCompositor</CODE> operation to Composition's basic glyphinterface.</P><A NAME="strat-lexi"></A><H3>Strategy Pattern</H3><A NAME="auto1047"></A><P>Encapsulating an algorithm in an object is the intent of the<A HREF="pat5ifs.htm" TARGET="_mainDisplayFrame">Strategy (315)</A> pattern. The key participants in thepattern are Strategy objects (which encapsulate different algorithms)and the context in which they operate. Compositors are strategies;they encapsulate different formatting algorithms. A composition is thecontext for a compositor strategy.</P><A NAME="auto1048"></A><P>The key to applying the Strategy pattern is designing interfaces forthe strategy and its context that are general enough to support arange of algorithms. You shouldn't have to change the strategy orcontext interface to support a new algorithm. In our example, thebasic Glyph interface's support for child access, insertion, andremoval is general enough to let Compositor subclasses change thedocument's physical structure, regardless of the algorithm they use todo it. Likewise, the Compositor interface gives compositions whateverthey need to initiate formatting.</P><A NAME="sec2-4"></A><H2><A HREF="#sec2-5"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Supporting Multiple Look-and-Feel Standards"></A>Embellishing the User Interface</H2><A NAME="auto1049"></A><P>We consider two embellishments in Lexi's user interface. Thefirst adds a border around the text editing area to demarcate the pageof text. The second adds scroll bars that let the user view differentparts of the page. To make it easy to add and remove theseembellishments (especially at run-time), we shouldn't use inheritanceto add them to the user interface. We achieve the most flexibilityif other user interface objects don't even know the embellishments arethere. That will let us add and remove the embellishments withoutchanging other classes.</P><A NAME="transparentencl"></A><H3>Transparent Enclosure</H3><A NAME="auto1050"></A><P>From a programming point of view, embellishing the user interface involvesextending existing code. Using inheritance to do such extension precludesrearranging embellishments at run-time, but an equally serious problem isthe explosion of classes that can result from an inheritance-basedapproach.</P><A NAME="compcomposite"></A><P>We could add a border to Composition by subclassing it to yield aBorderedComposition class. Or we could add a scrolling interface inthe same way to yield a ScrollableComposition. If we want both scrollbars and a border, we might produce a BorderedScrollableComposition,and so forth. In the extreme, we end up with a class for everypossible combination of embellishments, a solution that quicklybecomes unworkable as the variety of embellishments grows.</P><A NAME="auto1051"></A><P>Object composition offers a potentially more workable and flexibleextension mechanism. But what objects do we compose? Since we knowwe're embellishing an existing glyph, we could make the embellishmentitself an object (say, an instance of class <STRONG>Border</STRONG>). Thatgives us two candidates for composition, the glyph and the border. Thenext step is to decide who composes whom. We could have the bordercontain the glyph, which makes sense given that the border willsurround the glyph on the screen. Or we could do the opposite—putthe border into the glyph—but then we must make modifications to thecorresponding Glyph subclass to make it aware of the border. Our firstchoice, composing the glyph in the border, keeps the border-drawingcode entirely in the Border class, leaving other classes alone.</P><A NAME="auto1052"></A><P>What does the Border class look like? The fact that borders have anappearance suggests they should actually be glyphs; that is, Bordershould be a subclass of Glyph. But there's a more compelling reasonfor doing this: Clients shouldn't care whether glyphs have borders ornot. They should treat glyphs uniformly. When clients tell a plain,unbordered glyph to draw itself, it should do so withoutembellishment. If that glyph is composed in a border, clientsshouldn't have to treat the border containing the glyph anydifferently; they just tell it to draw itself as they told the plainglyph before. This implies that the Border interface matches the Glyphinterface. We subclass Border from Glyph to guarantee thisrelationship.</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -