设计模式可复用面向对象软件的基础04.pdf
《设计模式可复用面向对象软件的基础04.pdf》由会员分享,可在线阅读,更多相关《设计模式可复用面向对象软件的基础04.pdf(56页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第4章结构型模式结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。这一模式尤其有助于多个独立开发的类库协同工作。另外一个例子是类形式的A d a p t e r(4.1)模式。一般来说,适配器使得一个接口(a d a p t e e的接口)与其他接口兼容,从而给出了多个不同接口的统一抽象。为此,类适配器对一个 a d a p t e e类进行私有继承。这样,适配器就可以用a d a p t e e的接口表示它的接口。结构型对象模式不是对接口和实现进行组合,而是描
2、述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。Composite(4.3)模式是结构型对象模式的一个实例。它描述了如何构造一个类层次式结构,这一结构由两种类型的对象(基元对象和组合对象)所对应的类构成.其中的组合对象使得你可以组合基元对象以及其他的组合对象,从而形成任意复杂的结构。在 Proxy(4.7)模式中,p r o x y对象作为其他对象的一个方便的替代或占位符。它的使用可以有多种形式。例如它可以在局部空间中代表一个远程地址空间中的对象,也可以表示一个要求被加载的较大的对
3、象,还可以用来保护对敏感对象的访问。P r o x y模式还提供了对对象的一些特有性质的一定程度上的间接访问,从而它可以限制、增强或修改这些性质。F l y w e i g h t(4.6)模式为了共享对象定义了一个结构。至少有两个原因要求对象共享:效率和一致性。F l y w e i g h t的对象共享机制主要强调对象的空间效率。使用很多对象的应用必需考虑每一个对象的开销。使用对象共享而不是进行对象复制,可以节省大量的空间资源。但是仅当这些对象没有定义与上下文相关的状态时,它们才可以被共享。F l y w e i g h t的对象没有这样的状态。任何执行任务时需要的其他一些信息仅当需要时才
4、传递过去。由于不存在与上下文相关的状态,因此F l y w e i g h t对象可以被自由地共享。如果说F l y w e i g h t模式说明了如何生成很多较小的对象,那么 F a c a d e(4.5)模式则描述了如何用单个对象表示整个子系统。模式中的 f a c a d e用来表示一组对象,f a c a d e的职责是将消息转发给它所表示的对象。B r i d g e(4.2)模式将对象的抽象和其实现分离,从而可以独立地改变它们。D e c o r a t o r(4.4)模式描述了如何动态地为对象添加职责。D e c o r a t o r模式是一种结构型模式。这一模式采用递归
5、方式组合对象,从而允许你添加任意多的对象职责。例如,一个包含用户界面组件的D e c o r a t o r对象可以将边框或阴影这样的装饰添加到该组件中,或者它可以将窗口滚动和缩放这样的功能添加的组件中。我们可以将一个 D e c o r a t o r对象嵌套在另外一个对象中就可以很简单地增加两个装饰,添加其他的装饰也是如此。因此,每个 D e c o r a t o r对象必须与其组件的接口兼容并且保证将消息传递给它。D e c o r a t o r模式在转发一条信息之前或之后都可以完成它的工作(比如绘制组件的边框)。许多结构型模式在某种程度上具有相关性,我们将在本章末讨论这些关系。4.
6、1 ADAPTER(适配器)类对象结构型模式1.意图将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。2.别名包装器 Wr a p p e r。3.动机有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配。例如,有一个绘图编辑器,这个编辑器允许用户绘制和排列基本图元(线、多边型和正文等)生成图片和图表。这个绘图编辑器的关键抽象是图形对象。图形对象有一个可编辑的形状,并可以绘制自身。图形对象的接口由一个称为 S h a p e的抽象类定义。绘图编辑器为每一种图形对象定义了
7、一个 S h a p e的子类:L i n e S h a p e类对应于直线,P o l y g o n S h a p e类对应于多边型,等等。像L i n e S h a p e和P o l y g o n S h a p e这样的基本几何图形的类比较容易实现,这是由于它们的绘图和编辑功能本来就很有限。但是对于可以显示和编辑正文的 Te x t S h a p e子类来说,实现相当困难,因为即使是基本的正文编辑也要涉及到复杂的屏幕刷新和缓冲区管理。同时,成品的用户界面工具箱可能已经提供了一个复杂的 Te x t Vi e w类用于显示和编辑正文。理想的情况是我们可以复用这个 Te x t
8、 Vi e w类以实现 Te x t S h a p e类,但是工具箱的设计者当时并没有考虑S h a p e的存在,因此Te x t Vi e w和S h a p e对象不能互换。一个应用可能会有一些类具有不同的接口并且这些接口互不兼容,在这样的应用中象Te x t Vi e w这样已经存在并且不相关的类如何协同工作呢?我们可以改变 Te x t Vi e w类使它兼容S h a p e类的接口,但前提是必须有这个工具箱的源代码。然而即使我们得到了这些源代码,修改Te x t Vi e w也是没有什么意义的;因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口。我们可以
9、不用上面的方法,而定义一个 Te x t S h a p e类,由它来适配Te x t Vi e w的接口和S h a p e的接口。我们可以用两种方法做这件事:1)继承S h a p e类的接口和Te x t Vi e w的实现,或2)将一个Te x t Vi e w实例作为Te x t S h a p e的组成部分,并且使用Te x t Vi e w的接口实现Te x t S h a p e。这两种方法恰恰对应于A d a p t e r模式的类和对象版本。我们将Te x t S h a p e称之为适配器A d a p t e r。9 2设计模式:可复用面向对象软件的基础上面的类图说明了
10、对象适配器实例。它说明了在 S h a p e类中声明的B o u n d i n g B o x请求如何被转换成在Te x t Vi e w类中定义的G e t E x t e n t请求。由于Te x t S h a p e将Te x t Vi e w的接口与S h a p e的接口进行了匹配,因此绘图编辑器就可以复用原先并不兼容的 Te x t Vi e w类。A d a p t e r时常还要负责提供那些被匹配的类所没有提供的功能,上面的类图中说明了适配器如何实现这些职责。由于绘图编辑器允许用户交互的将每一个 S h a p e对象“拖动”到一个新的位置,而Te x t Vi e w设
11、计中没有这种功能。我们可以实现 Te x t S h a p e类的C r e a t e M a n i p u l a t o r操作,从而增加这个缺少的功能,这个操作返回相应的 M a n i p u l a t o r子类的一个实例。M a n i p u l a t o r是一个抽象类,它所描述的对象知道如何驱动 S h a p e类响应相应的用户输入,例如将图形拖动到一个新的位置。对应于不同形状的图形,M a n i p u l a t o r有不同的子类;例如子类Te x t M a n i p u l a t o r对应于Te x t S h a p e。Te x t S h
12、a p e通过返回一个Te x t M a n i p u l a t o r实例,增加了Te x t Vi e w中缺少而S h a p e需要的功能。4.适用性以下情况使用A d a p t e r模式 你想使用一个已经存在的类,而它的接口不符合你的需求。你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。(仅适用于对象A d a p t e r)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。5.结构类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示。对象匹配器依
13、赖于对象组合,如下图所示。6.参与者 Ta r g e t(S h a p e)定义C l i e n t使用的与特定领域相关的接口。C l i e n t(D r a w i n g E d i t o r)第4章结构型模式9 3 与符合Ta rg e t接口的对象协同。A d a p t e e(Te x t Vi e w)定义一个已经存在的接口,这个接口需要适配。A d a p t e r(Te x t S h a p e)对A d a p t e e的接口与Ta rg e t接口进行适配7.协作 Client在A d a p t e r实例上调用一些操作。接着适配器调用 A d a p
14、 t e e的操作实现这个请求。8.效果类适配器和对象适配器有不同的权衡。类适配器 用一个具体的A d a p t e r类对A d a p t e e和Ta rg e t进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类A d a p t e r将不能胜任工作。使得A d a p t e r可以重定义A d a p t e e的部分行为,因为A d a p t e r是A d a p t e e的一个子类。仅仅引入了一个对象,并不需要额外的指针以间接得到 a d a p t e e。对象适配器则 允许一个A d a p t e r与多个A d a p t e e即A d a p t
15、e e本身以及它的所有子类(如果有子类的话)同时工作。A d a p t e r也可以一次给所有的A d a p t e e添加功能。使得重定义A d a p t e e的行为比较困难。这就需要生成 A d a p t e e的子类并且使得A d a p t e r引用这个子类而不是引用A d a p t e e本身。使用A d a p t e r模式时需要考虑的其他一些因素有:1)Adapter的匹配程度对A d a p t e e的接口与Ta rg e t的接口进行匹配的工作量各个A d a p t e r可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名)到支持完全不同的操作集
16、合。A d a p t e r的工作量取决于Ta rg e t接口与A d a p t e e接口的相似程度。2)可插入的Adapter 当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。O b j e c t-Wo r k/S m a l l t a l k P a r 9 0 使用pluggable adapter一词描述那些具有内部接口适配的类。考虑Tr e e D i s p l a y窗
17、口组件,它可以图形化显示树状结构。如果这是一个具有特殊用途的窗口组件,仅在一个应用中使用,我们可能要求它所显示的对象有一个特殊的接口,即它们都是抽象类Tr e e的子类。如果我们希望使 Tr e e D i s p l a y有具有良好的复用性的话(比如说,我们希望将它作为可用窗口组件工具箱的一部分),那么这种要求将是不合理的。应用程序将自己定义树结构类,而不应一定要使用我们的抽象类 Tr e e。不同的树结构会有不同的接口。例如,在一个目录层次结构中,可以通过 G e t S u b d i r e c t o r i e s操作进行访问子目录,然而在一个继承式层次结构中,相应的操作可能被称
18、为 G e t S u b c l a s s e s。尽管这两种层次结构使用的接口不同,一个可复用的 Tr e e D i s p l a y窗口组件必须能显示所有这两种结构。也就是说,Tr e e D i s p l a y应具有接口适配的功能。我们将在实现一节讨论在类中构建接口适配的多种方法。3)使用双向适配器提供透明操作使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 A d a p t e e的接口,因此并不是所有 A d a p t e e对象可以被使用的9 4设计模式:可复用面向对象软件的基础地方它都可以被使用。双向适配器提供了这样的透明性。在两个不同的
19、客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。考虑一个双向适配器,它将图形编辑框架Unidraw VL90与约束求解工具箱 Q O C A H H M V 9 2 集成起来。这两个系统都有一些类,这些类显式地表示变量:U n i d r a w含有类S t a t e Va r i a b l e,Q O C A中含有类C o n s t r a i n t Va r i a b l e,如下图所示。为了使U n i d r a w与Q O C A协同工作,必须首先使类C o n s t r a i n t Va r i a b l e与类S t a t e Va r i a b
20、l e相匹配;而为了将Q O C A的求解结果传递给U n i d r a w,必须使S t a t e Va r i a b l e与C o n s t r a i n t Va r i a b l e相匹配。这一方案中包含了一个双向适配器 C o n s t r a i n t S t a t e Va r i a b l e,它是类C o n s t r a i n t Va r i a b l e与类S t a t e Va r i a b l e共同的子类,C o n s t r a i n t S t a t e Va r i a b l e使得两个接口互相匹配。在该例中多重继承是
21、一个可行的解决方案,因为被适配类的接口差异较大。双向适配器与这两个被匹配的类都兼容,在这两个系统中它都可以工作。9.实现尽管A d a p t e r模式的实现方式通常简单直接,但是仍需要注意以下一些问题:1)使用C +实现适配器类在使用C+实现适配器类时,A d a p t e r类应该采用公共方式继承Ta rg e t类,并且用私有方式继承 A d a p t e e类。因此,A d a p t e r类应该是Ta rg e t的子类型,但不是A d a p t e e的子类型。2)可插入的适配器有许多方法可以实现可插入的适配器。例如,前面描述的Tr e e D i s p l a y窗口
22、组件可以自动的布置和显示层次式结构,对于它有三种实现方法:首先(这也是所有这三种实现都要做的)是为 Adaptee 找到一个“窄”接口,即可用于适配的最小操作集。因为包含较少操作的窄接口相对包含较多操作的宽接口比较容易进行匹配。对于Tr e e D i s p l a y而言,被匹配的对象可以是任何一个层次式结构。因此最小接口集合仅包含两个操作:一个操作定义如何在层次结构中表示一个节点,另一个操作返回该节点的子节点。对这个窄接口,有以下三个实现途径:a)使用抽象操作在Tr e e D i s p l a y类中定义窄A d a p t e e接口相应的抽象操作。这样就由子类来实现这些抽象操作并
23、匹配具体的树结构的对象。例如,D i r e c t o r y Tr e e D i s p l a y子类将通过访问目录结构实现这些操作,如下图所示。第4章结构型模式9 5(到QOCA类层次结构)(到Unidraw 类层次结构)D i r e c t o r y Tr e e D i s p l a y对这个窄接口加以特化,使得它的 D i r e c t o r y B r o w s e r客户可以用它来显示目录结构。b)使用代理对象在这种方法中,Tr e e D i s p l a y将访问树结构的请求转发到代理对象。Tr e e D i s p l a y的客户进行一些选择,并将这
24、些选择提供给代理对象,这样客户就可以对适配加以控制,如下图所示。例如,有一个D i r e c t o r y B r o w s e r,它像前面一样使用 Tr e e D i s p l a y。D i r e c t o r y B r o w s e r可能为匹配Tr e e D i s p l a y和层次目录结构构造出一个较好的代理。在 S m a l l t a l k或Objective C这样的动态类型语言中,该方法只需要一个接口对适配器注册代理即可。然后 Tr e e D i s p l a y简单地将请求转发给代理对象。N E X T S T E P A d d 9 4
25、大量使用这种方法以减少子类化。在C+这样的静态类型语言中,需要一个代理的显式接口定义。我们将 Tr e e D i s p l a y需要的窄接口放入纯虚类 Tr e e A c c e s s o r D e l e g a t e中,从而指定这样的一个接口。然后我们可以运用继承机制将这个接口融合到我们所选择的代理中这里我们选择 D i r e c t o r y B r o w s e r。如果D i r e c t o r y B r o w s e r没有父类我们将采用单继承,否则采用多继承。这种将类融合在一起的方法相对于引入一个新的Tr e e D i s p l a y子类并单独实
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 设计 模式 可复用 面向 对象 软件 基础 04
限制150内