《设计模式可复用面向对象软件的基础04.pdf》由会员分享,可在线阅读,更多相关《设计模式可复用面向对象软件的基础04.pdf(56页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
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子类并单独实
26、现它的操作的方法要容易一些。c)参数化的适配器通常在S m a l l t a l k中支持可插入适配器的方法是,用一个或多个模块对适配器进行参数化。模块构造支持无子类化的适配。一个模块可以匹配一个请求,并且适配器可以为每个请求存储一个模块。在本例中意味着,Tr e e D i s p l a y存储的一个模块用来将一个节点转化成为一个G r a p h i c N o d e,另外一个模块用来存取一个节点的子节点。例如,当对一个目录层次建立Tr e e D i s p l a y时,我们可以这样写:如果你在一个类中创建接口适配,这种方法提供了另外一种选择,它相对于子类化方法来说更方便一些。1
27、0.代码示例对动机一节中例子,从类 S h a p e和Te x t Vi e w开始,我们将给出类适配器和对象适配器实现代码的简要框架。9 6设计模式:可复用面向对象软件的基础S h a p e假定有一个边框,这个边框由它相对的两角定义。而 Te x t Vi e w则由原点、宽度和高度定义。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对象知道如何驱动这个图形。Te x t Vi e w没有等同的操作。Te x t
28、S h a p e类是这些不同接口间的适配器。类适配器采用多重继承适配接口。类适配器的关键是用一个分支继承接口,而用另外一个分支继承接口的实现部分。通常 C+中作出这一区分的方法是:用公共方式继承接口;用私有方式继承接口的实现。下面我们按照这种常规方法定义 Te x t S h a p e适配器。B o u n d i n g B o x操作对Te x t Vi e w的接口进行转换使之匹配S h a p e的接口。I s E m p t y操作给出了在适配器实现过程中常用的一种方法:直接转发请求:最后,我们定义C r e a t e M a n i p u l a t o r(Te x t
29、Vi e w不支持该操作),假定我们已经实现了支持Te x t S h a p e操作的类Te x t M a n i p u l a t o r。第4章结构型模式9 7C r e a t e M a n i p u l a t o r是一个Factory Method的实例。对象适配器采用对象组合的方法将具有不同接口的类组合在一起。在该方法中,适配器Te x t S h a p e维护一个指向Te x t Vi e w的指针。Te x t S h a p e必须在构造器中对指向 Te x t Vi e w实例的指针进行初始化,当它自身的操作被调用时,它还必须对它的 Te x t Vi e w
30、对象调用相应的操作。在本例中,假设客户创建了 Te x t Vi e w对象并且将其传递给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的实现代码与类适配器版本的实现代码一样,因为它的实现从零开始,没有复用任何Te x t Vi e w已有的函数。将这段代码与类适配器的相应代码进行比较,可以看出编写对象适配器代码相对麻烦一些,但是它比较灵活。例如,客户仅需将 Te x t Vi e w子类的一个实例传给Te x t S h a p e类的构造函数,对象适配器版本的Te x t S h a p e就同样可以与Te x t Vi e w
31、子类一起很好的工作。11.已知应用意图一节的例子来自一个基于 E T+W G M 8 8 的绘图应用程序E T+D r a w,E T+D r a w通过使用一个Te x t S h a p e适配器类的方式复用了E T+中一些类,并将它们用于正文编辑。I n t e r Vi e w 2.6为诸如 s c r o l l b a r s、b u t t o n s和m e n u s的用户界面元素定义了一个抽象类I n t e r a c t o r V L 8 8,它同时也为l i n e、c i r c l e、p o l y g o n和s p l i n e这样的结构化图形对象定义了
32、一个抽象类G r a p h i c s。I n t e r a c t o r和G r a p h i c s都有图形外观,但它们有着不同的接口和实现(它们没有同一个父类),因此它们并不兼容。也就是说,你不能直接将一个结构化的图形对象嵌入9 8设计模式:可复用面向对象软件的基础一个对话框中。而I n t e r Vi e w 2.6定义了一个称为G r a p h i c B l o c k的对象适配器,它是I n t e r a c t o r的子类,包含G r a p h i c类的一个实例。G r a p h i c B l o c k将G r a p h i c类的接口与 I n t
33、 e r a c t o r类的接口进行匹配。G r a p h i c B l o c k使得一个G r a p h i c的实例可以在I n t e r a c t o r结构中被显示、滚动和缩放。可插入的适配器在O b j e c t Wo r k s/S m a l l t a l k P a r 9 0 中很常见。标准S m a l l t a l k为显示单个值的视图定义了一个Va l u e M o d e l类。为访问这个值,Va l u e M o d e l定义了一个“v a l u e”和“v a l u e:”接口。这些都是抽象方法。应用程序员用与特定领域相关的名字访问
34、这个值,如“w i d t h”和“w i d t h:”,但为了使特定领域相关的名字与Va l u e M o d e l的接口相匹配,他们不一定要生成Va l u e M o d e l的子类。而O b j e c t Wo r k s/S m a l l t a l k包含了一个 Va l u e M o d e l类的子类,称为 P l u g g a b l e A d a p t o r。P l u g g a b l e A d a p t o r对象可以将其他对象与Va l u e M o d e l的接口(“v a l u e”和“v a l u e:”)相匹配。它可以用模块
35、进行参数化,以便获取和设置所期望的值。P l u g g a b l e A d a p t o r在其内部使用这些模块以实现“v a l u e”和“v a l u e:”接口,如下图所示。为语法上方便起见,P l u g g a b l e A d a p t o r也允许你直接传递选择器的名字(例如“w i d t h”和“w i d t h:”),它自动将这些选择器转换为相应的模块。另外一个来自O b j e c t Wo r k s/S m a l l t a l k的例子是Ta b l e A d a p t o r类,它可以将一个对象序列与一个表格表示相匹配。这个表格在每行显示一
36、个对象。客户用表格可以使用的消息集对TableAdaptor 进行参数设置,从一个对象得到行属性。在N e X T的 A p p K i t A d d 9 4 中,一些类使用代理对象进行接口匹配。一个例子是类N X B r o w s e r,它可以显示层次式数据列表。N X B r o w s e r类用一个代理对象存取并适配数据。M a y e r的“Marriage of Convenience”M e y 8 8 是一种形式的类适配器。M a y e r描述了F i x e d S t a c k类如何匹配一个A r r a y类的实现部分和一个 S t a c k类的接口部分。结果
37、是一个包含一定数目项目的栈。12.相关模式模式B r i d g e(4.2)的结构与对象适配器类似,但是 B r i d g e模式的出发点不同:B r i d g e目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而 A d a p t e r则意味着改变一个已有对象的接口。D e c o r a t o r(4.4)模式增强了其他对象的功能而同时又不改变它的接口。因此 d e c o r a t o r对应用程序的透明性比适配器要好。结果是 d e c o r a t o r支持递归组合,而纯粹使用适配器是不可能实现这一点的。模式P r o x y(4.7)在
38、不改变它的接口的条件下,为另一个对象定义了一个代理。第4章结构型模式9 94.2 BRIDGE(桥接)对象结构型模式1.意图将抽象部分与它的实现部分分离,使它们都可以独立地变化。2.别名H a n d l e/B o d y3.动机当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但是此方法有时不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。让我们考虑在一个用户界面工具箱中,一个可移植的 Wi n d o w抽象部分的实现。例如,这一抽象部分应该允许用户开发一些在 X Wi
39、ndow System和I B M的Presentation Manager(PM)系统中都可以使用的应用程序。运用继承机制,我们可以定义Wi n d o w抽象类和它的两个子类XWi n d o w与P M Wi n d o w,由它们分别实现不同系统平台上的 Wi n d o w界面。但是继承机制有两个不足之处:1)扩展 Wi n d o w抽象使之适用于不同种类的窗口或新的系统平台很不方便。假设有Wi n d o w的一个子类I c o n Wi n d o w,它专门将Wi n d o w抽象用于图标处理。为了使 I c o n Wi n d o w支持两个系统平台,我们必须实现两个新
40、类 X I c o n Wi n d o w和P M I c o n Wi n d o w,更为糟糕的是,我们不得不为每一种类型的窗口都定义两个类。而为了支持第三个系统平台我们还必须为每一种窗口定义一个新的Wi n d o w子类,如下图所示。2)继承机制使得客户代码与平台相关。每当客户创建一个窗口时,必须要实例化一个具体的类,这个类有特定的实现部分。例如,创建 X w i n d o w对象会将Wi n d o w抽象与X Wi n d o w的实现部分绑定起来,这使得客户程序依赖于 X Wi n d o w的实现部分。这将使得很难将客户代码移植到其他平台上去。客户在创建窗口时应该不涉及到其
41、具体实现部分。仅仅是窗口的实现部分依赖于应用运行的平台。这样客户代码在创建窗口时就不应涉及到特定的平台。B r i d g e模式解决以上问题的方法是,将 Wi n d o w抽象和它的实现部分分别放在独立的类层次结构中。其中一个类层次结构针对窗口接口(Wi n d o w、I c o n Wi n d o w、Tr a n s i e n t Wi n d o w),另外一个独立的类层次结构针对平台相关的窗口实现部分,这个类层次结构的根类为Wi n d o w I m p。例如X w i n d o w I m p子类提供了一个基于X Wi n d o w系统的实现,如下页上图所示。对Wi
42、n d o w子类的所有操作都是用Wi n d o w I m p接口中的抽象操作实现的。这就将窗口的抽象与系统平台相关的实现部分分离开来。因此,我们将 Wi n d o w与Wi n d o w I m p之间的关系称之为桥接,因为它在抽象类与它的实现之间起到了桥梁作用,使它们可以独立地变化。1 0 0设计模式:可复用面向对象软件的基础4.适用性以下一些情况使用B r i d g e模式:你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时 B r i d g
43、e模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。(C+)你想对客户完全隐藏抽象的实现部分。在 C+中,类的表示在类接口中是可见的。正如在意图一节的第一个类图中所示的那样,有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。R u m b a u g h称这种类层次结构为“嵌套的普化”(nested generalizations)。你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是C o p l i e n的S t r i n g类 C
44、o p 9 2,在这个类中多个对象可以共享同一个字符串表示(S t r i n g R e p)。5.结构第4章结构型模式1 0 16.参与者 Abstraction(Wi n d o w)定义抽象类的接口。维护一个指向I m p l e m e n t o r类型对象的指针。RefinedAbstraction(IconWi n d o w)扩充由A b s t r a c t i o n定义的接口。Implementor(Wi n d o w I m p)定义实现类的接口,该接口不一定要与 A b s t r a c t i o n的接口完全一致;事实上这两个接口可以完全不同。一般来讲,I
45、 m p l e m e n t o r接口仅提供基本操作,而 A b s t r a c t i o n则定义了基于这些基本操作的较高层次的操作。ConcreteImplementor(XwindowImp,PMWi n d o w I m p)实现I m p l e m e n t o r接口并定义它的具体实现。7.协作 Abstraction将c l i e n t的请求转发给它的I m p l e m e n t o r对象。8.效果B r i d g e模式有以下一些优点:1)分离接口及其实现部分一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以
46、在运行时刻改变它的实现。将A b s t r a c t i o n与I m p l e m e n t o r分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 A b s t r a c t i o n类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道A b s t r a c t i o n和I m p l e m e n t o r即可。2)提高可扩充性你可以独立地对A b s t r a c t i o n和I m p l e m e n t o
47、 r层次结构进行扩充。3)实现细节对客户透明你可以对客户隐藏实现细节,例如共享 I m p l e m e n t o r对象以及相应的引用计数机制(如果有的话)。9.实现使用B r i d g e模式时需要注意以下一些问题:1)仅有一个Implementor 在仅有一个实现的时候,没有必要创建一个抽象的 I m p l e m e n t o r类。这是B r i d g e模式的退化情况;在 A b s t r a c t i o n与I m p l e m e n t o r之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序时,模式的分离机制还是非常有用的也
48、就是说,不必重新编译它们,仅需重新连接即可。C a r o l a n C a r 8 9 用“常露齿嘻笑的猫”(Cheshire Cat)描述这一分离机制。在 C+中,I m p l e m e n t o r类的类接口可以在一个私有的头文件中定义,这个文件不提供给客户。这样你就对客户彻底隐藏了一个类的实现部分。2)创建正确的I m p l e m e n t o r对象当存在多个I m p l e m e n t o r类的时候,你应该用何种方法,在何时何处确定创建哪一个I m p l e m e n t o r类呢?如果A b s t r a c t i o n知道所有的C o n c
49、r e t e I m p l e m e n t o r类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。例如,如果一个1 0 2设计模式:可复用面向对象软件的基础c o l l e c t i o n类支持多重实现,就可以根据 c o l l e c t i o n的大小决定实例化哪一个类。链表的实现可以用于较小的c o l l e c t i o n类,而h a s h表则可用于较大的c o l l e c t i o n类。另外一种方法是首先选择一个缺省的实现,然后根据需要改变这个实现。例如,如果一个c o l l e c t i o
50、n的大小超出了一定的阈值时,它将会切换它的实现,使之更适用于表目较多的c o l l e c t i o n。也可以代理给另一个对象,由它一次决定。在 Wi n d o w/Wi n d o w I m p的例子中,我们可以引入一个f a c t o r y对象(参见Abstract Factory(3.1)),该对象的唯一职责就是封装系统平台的细节。这个对象知道应该为所用的平台创建何种类型的 Wi n d o w I m p对象;Wi n d o w仅需向它请求一个Wi n d o w I m p,而它会返回正确类型的Wi n d o w I m p对象。这种方法的优点是 Abstracti