设计模式可复用面向对象软件的基础02.pdf
《设计模式可复用面向对象软件的基础02.pdf》由会员分享,可在线阅读,更多相关《设计模式可复用面向对象软件的基础02.pdf(32页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第2章实例研究:设计一个文档编辑器这一章将通过设计一个称为 L e x i的“所见即所得”(或“W Y S I W Y G”)的文档编辑器,来介绍设计模式的实际应用。我们将会看到在 L e x i和类似应用中,设计模式是怎样解决设计问题的。在本章最后,通过这个例子的学习你将获得 8个模式的实用经验。图2-1是L e x i的用户界面。文档的所见即所得的表示占据了中间的大矩形区域。文档能够以不同的格式风格自由混合文本和图形。文档的周围是通常的下拉菜单和滚动条,以及一些用来跳到特定页的页码图标。图2-1 Lexi的用户界面L e x i的设计是基于C a l d e r开发的文本编辑应用D o c
2、的。C L 9 2 第2章实例研究:设计一个文档编辑器2 32.1 设计问题我们将考察L e x i设计中的7个问题:1)文档结构对文档内部表示的选择几乎影响L e x i设计的每个方面。所有的编辑、格式安排、显示和文本分析都涉及到这种表示。我们怎样组织这个信息会影响到应用的其他方面。2)格式化L e x i是怎样将文本和图形安排到行和列上的?哪些对象负责执行不同的格式策略?这些策略又是怎样和内部表述相互作用的?3)修饰用户界面L e x i的用户界面包括滚动条、边界和用来修饰 W Y S I W Y G文档界面的阴影。这些修饰有可能随着 L e x i用户界面的演化而发生变化。因此,在不影响
3、应用其他方面的情况下,能自由增加和去除这些修饰就十分重要了。4)支持多种视感(l o o k-a n d-f e e l)标准L e x i应不需作较大修改就能适应不同的视感标准,如M o t i f和Presentation Manager(PM)等。5)支持多种窗口系统不同的视感标准通常是在不同的窗口系统上实现的。L e x i的设计应尽可能的独立于窗口系统。6)用户操作用户通过不同的用户界面控制L e x i,包括按钮和下拉菜单。这些界面对应的功能分散在整个应用对象中。这里的难点在于提供一个统一的机制,既可以访问这些分散的功能,又可以对操作进行撤消(u n d o)。7)拼写检查和连字符
4、L e x i是怎样支持像检查拼写错误和决定连字符的连字点这样的分析操作的?当我们不得不添加一个新的分析操作时,我们怎样尽量少修改相关的类?我们将在下面的各节里讨论这些设计问题。每个问题都有一组相关联的目标集合和我们怎样达到这些目标的限制条件集合。在给出特定解决方案之前,我们会详细解释设计问题的目标和限制条件。问题和其解决方案会列举一个或多个设计模式。对每个问题的讨论将在对相关设计模式的简单介绍后结束。2.2 文档结构从根本上来说,一个文档只是对字符、线、多边形和其他图形元素的一种安排。这些元素记录了文档的整个信息内容。然而,一个文档作者通常并不将这些元素看作图形项,而是看作文档的物理结构行、
5、列、图形、表和其他子结构。而这些子结构也有自己的子结构。L e x i的用户界面应该让用户直接操纵这些子结构。例如,一个用户应该能够将一个图表当作一个单元,而不是个别图形原语的一组集合。用户应该能够对表进行整体引用,而不是将表作为非结构化的一堆文本和图形。这有助于使界面简单和直观。为了使L e x i的实现具有类似的性质,我们选择能匹配文档物理结构的内部表示。特别的,内部表示应支持如下几点:保持文档的物理结构。即将文本和图形安排到行、列、表等。可视化生成和显示文档。根据显示位置来映射文档内部表示的元素。这可以使 L e x i根据用户在可视化表示中所点击的某个东西来决定用户所引用的文档元素。作
6、者也常从逻辑结构来看文档,即看成句子、段落、节、小节和章。为了使这个例子简单,我们的文档内部表示不显式储存逻辑结构信息。但是我们描述的设计方案同样适用于表述逻辑结构信息的情况。除了这些目标外,还有一些限制条件。首先,我们应该一致对待文本和图形。应用界面允许用户在图形中自由的嵌入文本,反之亦然。我们应该避免将图形看作文本的一种特殊情形,或将文本看作图形的特例。否则,我们最后得到的是冗余的格式和操纵机制。机制集合应该使文本和图形都能满足。其次,我们的实现不应该过分强调内部表示中单个元素和元素组之间的差别。L e x i应该能够一致地对待简单元素和组合元素,这样就允许任意复杂的文档。例如,第 5行第
7、2列的第1 0个元素既可以是一个字符,也可以是一个由许多子元素组成的复杂图表。一旦我们知道这个元素能够画出自己并指定了它的区域,那么它怎样显示在页面上和它的显示位置的确定就并不困难了。然而,为了检查拼写错误和确定连字符的连接点,需要对文本进行分析。这就与第二个限制条件产生了矛盾。我们通常并不关心一行上的元素是简单对象还是复杂对象,但是文本分析有时候依赖于被分析的对象。例如,检查多边形的拼写或以连字符连接它是没有意义的。文档内部表示设计应该考虑和权衡这个或其他潜在的彼此矛盾的限制条件。2.2.1 递归组合层次结构信息的表述通常是通过一种被称为递归组合(Recursive Composition)
8、的技术来实现的。递归组合可以由较简单的元素逐渐建立复杂的元素,是我们通过简单图形元素构造文档的方法之一。第一步,我们将字符和图形从左到右排列形成文档的一行,然后由多行形成一列,再由多列形成一页,等等,见图 2-2。图2-2 包含正文和图形的递归组合我们将每一个重要元素表示成一个对象,就可以描述这种物理结构。它不仅包括字符、图形等可见元素,也包括不可见的、结构化的元素,如行和列。结果就是如图2-3所示的对象结构。通过用对象表示文档的每一个字符和图形元素,我们可以提高L e x i最佳设计的灵活性。2 4设计模式:可复用面向对象软件的基础字符空格图组合(行)组合(列)第2章实例研究:设计一个文档编
9、辑器2 5我们能够在显示、格式化和互相嵌入等方面一致对待图形和文本。我们能够扩展L e x i以支持新的字符集而不会影响其他功能。L e x i的对象结构与文档的物理结构非常相像。图2-3 递归组合的对象结构这里隐含了两个重要的地方。第一个很明显,对象需要相应的类。第二个就不那么明显了,因为我们要一致性地对待这些对象,所以这些类必须有兼容的接口。在像C+这样的语言中,可以通过继承来关联类,使得接口兼容。2.2.2 图元我们将为出现在文档结构中的所有对象定义一个抽象类图元(G l y p h)。它的子类既定义了基本的图形元素(像字符和图像),又定义了结构元素(像行和列)。图2-4描述了G l y
10、 p h类组合(列)组合(行)组合(行)空格C a l d e r第一个在这种上下文使用术语“Gl y p h”C L 9 0。大多数同时代的文档编辑器由于效率原因,并不是对一个字符就使用一个对象的。C a l d e r在他的论文 C a l 9 3 中论证了该方法的可行性。为了简单起见,我们将图元严格限制在类层次结构上,所以没有 C a l d e r的那么复杂。C a l d e r的图元还能减少存储开销,形成有向无环图结构。我们也可以使用F l y w e i g h t(4.6)模式来达到相同的效果,我们将把它作为留给读者的一个练习。图2-4 部分Glyph类层次2 6设计模式:可复
11、用面向对象软件的基础层次的部分表示,表2-1以C+表示法描述了基本的G l y p h接口。表2-1 基本G l y p h接口R e s p o n s i b i l i t yO p e r a t i o n sA p p e a r a n c eVirtual Void Draw(Wi n d o w*)Virtual Void Bounds(Rect&)hit detectionVirtual bool Intersects(Const Point&)S t r u c t u r eVirtual Void Insert(Glyph*,int)Virtual Void Remo
12、ve(Glyph*)Virtual Glyph*Child(int)Virtual Glyph*Parent()图元有三个基本责任,它们是 1)怎样画出自己,2)它们占用多大空间,3)它们的父图元和子图元是什么。G l y p h子类为了在窗口上表示自己,重新定义了 D r a w操作。调用D r a w时,它们传递一个引用给Wi n d o w对象。Wi n d o w类为了在屏幕窗口上表示文本和基本图形,定义了一些图形操作。一个G l y p h的子类R e c t a n g l e可能会像下面这样重定义D r a w:void Rectangle:Draw(Window*w)w-Dra
13、wRect(_x0,_y0,_x1,_y1);这里的_ x 0,_ y 0,_ x 1,_ y 1是R e c t a n g l e的数据成员,定义了矩形的对顶点。D r a w R e c t是Wi n d o w操作,用来在屏幕上显示矩形。父图元通常需要知道像子图元需要占用多大空间这样的信息,以把它和其他图元安排在一行上,保证不会互相覆盖(参见图2-2)。B o u n d s操作返回图元占用的矩形区域,它返回的是包含该图元的最小矩形的对角顶点。G l y p h各子类重定义该操作,返回它们各自画图所用的矩形区域。I n t e r s e c t s操作判断一个指定的点是否与图元相交。
14、任何时候用户点击文档某处时,L e x i都能调用该操作确定鼠标所在的图元或图元结构。R e c t a n g l e类重定义了该操作,用来计算矩形和给定点的相交。因为图元可以有子图元,所以我们需要一个公共的接口来添加、删除和访问这些子图元。例如,一个行的子图元是该行上的所有图元。I n s e r t操作在整数I n d e x指定的位置上插入一个图元。R e m o v e操作移去一个指定的子图元。C h i l d操作返回给定I n d e x的子图元(如果有的话),像行这样有子图元的图元应该内部使用C h i l d操作,而不是直接访问子数据结构。这样当你将数据结构由数组改为连接表时
15、,你也无需修改像D r a w这样重复作用于各个子图元的操作。类似的,P a r e n t操作提供一个标准的访问父图元的接口。L e x i的图元保存一个指向其父图元的指引,P a r e n t操作只简单的返回这个指引。为了使讨论简单化,我们这里特地使用最小化的接口。一个完备的接口应该包括管理颜色、字体和坐标转换等图形属性的操作,和管理更复杂子对象的操作。一个整数I n d e x可能并不是指定子图元的最好方法,它依赖于图元所用的数据结构。如果图元在连接表中储存子图元,那么使用连接表指针应该更有效。我们在 2.8节讨论文档分析的时候,将会给出索引问题的更好解决方案。第2章实例研究:设计一个
16、文档编辑器2 72.2.3 组合模式递归组合不仅可用来表示文档,我们还可以用它表示任何潜在复杂的、层次式的结构。C o m p o s i t e(4.3)模式描述了面向对象的递归组合的本质。现在是回到此模式并学习它的时候了,需要时再回头参考这个场景。2.3 格式化我们已经解决了文档物理结构的表示问题。接着,我们需要解决的问题是怎样构造一个特殊物理结构,该结构对应于一个恰当地格式化了的文档。表示和格式化是不同的,记录文档物理结构的能力并没有告诉我们怎样得到一个特殊格式化结构。这个责任大多在于L e x i,它必须将文本分解成行,将行分解成列等等。同时还要考虑用户的高层次的要求,例如,用户可能会
17、指定边界宽度、缩进大小和表格形式、是否隔行显示以及其他可能的许多格式限制条件。L e x i的格式化算法必须考虑所有这些因素。现在我们将“格式化”含义限制为将一个图元集合分解为若干行。下面我们可以互换使用术语“格式化”(f o r m a t t i n g)和“分行”(l i n e b r e a k i n g)。下面讨论的技术同样适用于将行分解为列和将列分解为页。2.3.1 封装格式化算法由于所有这些限制条件和许多细节问题,格式化过程不容易被自动化。这里有许多解决方法,实际上人们已经提出了各种各样具有不同能力和缺陷的格式化算法。因为L e x i是一个所见即所得编辑器,所以一个必须考虑
18、的重要权衡之处在于格式化的质量和格式化的速度之间的取舍。我们通常希望在不牺牲文档美观外表的前提下,能得到良好的反映速度。这种权衡受许多因素影响,而并不是所有因素在编译时刻都能确定的。例如,用户也许能忍受稍慢一点的响应速度,以换取较好的格式。这种选择也许导致了比当前算法更适用的彻底不同的格式化算法。另一个例子,更多实现驱动的权衡是在格式化速度和存储需求之间:很有可能为了缓存更多的信息而降低格式化速度。因为格式化算法趋于复杂化,因而可以考虑将它们包含于文档结构之中,但最好是将它们彻底独立于文档结构之外。理想情况下,我们能够自由地增加一个 G l y p h子类而不用考虑格式算法。反过来,增加一个格
19、式算法不应要求修改已有的图元类。这些特征要求我们设计的 L e x i易于改变格式化算法。最好能在运行时刻改变这个算法,如果难以实现,至少在编译时刻应该可以很方便地改变。我们可以将算法独立出来,并把它封装到对象中使其便于替代。更进一步,可以定义一个封装格式化算法的对象的类层次结构。类层次结构的根结点将定义支持许多格式化算法的接口,每个子类实现这个接口以执行特定的算法。那时就能让G l y p h子类对象自动使用给定算法对象来排列其子图元。2.3.2 Compositor和Composition我们为能封装格式化算法的对象定义一个 C o m p o s i t o r类。它的接口(见表 2-2
20、)可让用户可能更关心的是文档的逻辑结构句子、段落、小节、章节等等。相比而言,对物理结构就没有这样的兴趣了。大部分用户不在意段落中的换行发生在何处,只要该段落能正确格式化就行了。格式化列和页,也是这样的。因而用户最终只指定物理结构的高层限制条件,用来满足他们的艰难工作则由 L e x i去完成。2 8设计模式:可复用面向对象软件的基础c o m p o s i t o r获知何时去格式化哪些图元。它所格式化的图元是一个被称为 C o m p o s i t i o n的特定图元的各个子图元。一个 C o m p o s i t i o n在创建时得到一个C o m p o s i t o r子类
21、实例,并在必要的时候(如用户改变文档的时候)让C o m p o s i t o r对它的图元作 C o m p o s e操作。图 2-5描述了C o m p o s i t i o n类和C o m p o s i t o r类之间的关系。表2-2 基本C o m p o s i t o r接口责任操作格式化的内容void SetComposition(Composition*)何时格式化virtual void Compose()图2-5 Composition和Compositor类间的关系一个未格式化的C o m p o s i t i o n对象只包含组成文档基本内容的可见图元。它
22、并不包含像行和列这样的决定文档物理结构的图元。C o m p o s i t i o n对象只在刚被创建并以待格式化的图元进行初始化后,才处于这种状态。当C o m p o s i t i o n需要格式化时,调用它的C o m p o s i t o r的C o m p o s e操作。C o m p o s i t o r依次遍历C o m p o s i t i o n的各个子图元,根据分行算法插入新的行和列图元。图2-6显示了得到的对象结构。图中由 C o m p o s i t o r创建和插入到对象结构中的图元图2-6 对象结构反映Compositor制导的分行生成的行列行空格C
23、o m p o s i t o r为了计算换行必须知道字符图元的字符代码。在 2.8节,我们将会看到:怎样可以不在 G l y p h接口中添加一个特定于字符的操作,而多态地获得这个信息。以灰色背景显示。每一个C o m p o s i t o r子类都能实现一个不同的分行算法。例如,一个 S i m p l e C o m p o s i t o r可以执行得很快,而不考虑像文档“色彩”这样深奥的东西。好的色彩意味着文本和空白的平滑分布。一个Te X C o m p o s i t o r会实现完全的TEX算法 K n u 8 4,会考虑像色彩这样的东西,而以较长的格式化时间作为代价。C o
24、 m p o s i t o r-C o m p o s i t i o n类的分离确保了支持文档物理结构的代码和支持不同格式化算法的代码之间的分离。我们能增加新的 C o m p o s i t o r子类而不触及G l y p h类,反之亦然。事实上,我们通过给C o m p o s i t i o n的基本图元接口增加一个 S e t C o m p o s i t o r操作,即可在运行时刻改变分行算法。2.3.3 策略模式在对象中封装算法是 S t r a t e g y(5.9)模式的目的。模式的主要参与者是 S t r a t e g y对象(这些对象中封装了不同的算法)和它们的
25、操作环境。其实 C o m p o s i t o r就是S t r a t e g y。它们封装了不同的格式算法。C o m p o s i t i o n就是C o m p o s i t o r策略的环境。S t r a t e g y模式应用的关键点在于为 S t r a t e g y和它的环境设计足够通用的接口,以支持一系列的算法。你不必为了支持一个新的算法而改变 S t r a t e g y或它的环境。在我们的例子中,支持子图元访问、插入和删除操作的基本 G l y p h接口就足以满足一般的用户需求,不管 C o m p o s i t o r子类使用何种算法,都足以支持其对
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 设计 模式 可复用 面向 对象 软件 基础 02
限制150内