第二十四章结构型模式结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。这一模式尤其有助于多个独立开发的类库协同工作。另外一个例子是类形式的Adapter模式。一般来说,适配器使得一个接口(adaptee的接口)与其他接口兼容,从而给出了多个不同接口的统一抽象。为此,类适配器对一个adaptee类进行私有继承。这样,适配器就可以用adaptee的接口表示它的接口。结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。Composite模式是结构型对象模式的一个实例。它描述了如何构造一个类层次式结构,这一结构由两种类型的对象(基元对象和组合对象)所对应的类构成.其中的组合对象使得你可以组合基元对象以及其他的组合对象,从而形成任意复杂的结构。在Proxy模式中,Proxy对象作为其他对象的一个方便的替代或占位符。它的使用可以有多种形式。例如它可以在局部空间中代表一个远程地址空间中的对象,也可以表示一个要求被加载的较大的对象,还可以用来保护对敏感对象的访问。Proxy模式还提供了对对象的一些特有性质的一定程度上的间接访问,从而它可以限制、增强或修改这些性质。Flyweight模式为了共享对象定义了一个结构。至少有两个原因要求对象共享:效率和一致性。Flyweight的对象共享机制主要强调对象的空间效率。使用很多对象的应用必需考虑每一个对象的开销。使用对象共享而不是进行对象复制,可以节省大量的空间资源。但是仅当这些对象没有定义与上下文相关的状态时,它们才可以被共享。Flyweight的对象没有这样的状态。任何执行任务时需要的其他一些信息仅当需要时才传递过去。由于不存在与上下文相关的状态,因此Flyweight对象可以被自由地共享。如果说Flyweight模式说明了如何生成很多较小的对象,那么Facade模式则描述了如何用单个对象表示整个子系统。模式中的facade用来表示一组对象,facade的职责是将消息转发给它所表示的对象。Bridge模式将对象的抽象和其实现分离,从而可以独立地改变它们。Decorator模式描述了如何动态地为对象添加职责。Decorator模式是一种结构型模式。这一模式采用递归方式组合对象,从而允许你添加任意多的对象职责。例如,一个包含用户界面组件的Decorator对象可以将边框或阴影这样的装饰添加到该组件中,或者它可以将窗口滚动和缩放这样的功能添加到组件中。我们可以将一个Decorator对象嵌套在另外一个对象中就可以很简单地增加两个装饰,添加其他的装饰也是如此。因此,每个Decorator对象必须与其组件的接口兼容并且保证将消息传递给它。Decorator模式在转发一条信息之前或之后都可以完成它的工作(比如绘制组件的边框)。4.1BRIDGE(桥接)——对象结构型模式1.意图将抽象部分与它的实现部分分离,使它们都可以独立地变化。2.别名Handle/Body3.动机当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但是此方法有时不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。让我们考虑在一个用户界面工具箱中,一个可移植的Window抽象部分的实现。例如,这一抽象部分应该允许用户开发一些在XWindowSystem和IBM的PresentationManager(PM)系统中都可以使用的应用程序。运用继承机制,我们可以定义Window抽象类和它的两个子类XWindow与PMWindow,由它们分别实现不同系统平台上的Window界面。但是继承机制有两个不足之处:1)扩展Window抽象使之适用于不同种类的窗口或新的系统平台很不方便。假设有Window的一个子类IconWindow,它专门将Window抽象用于图标处理。为了使IconWindow支持两个系统平台,我们必须实现两个新类XIconWindow和PMIconWindow,更为糟糕的是,我们不得不为每一种类型的窗口都定义两个类。而为了支持第三个系统平台我们还必须为每一种窗口定义一个新的Window子类,如下图所示。2)继承机制使得客户代码与平台相关。每当客户创建一个窗口时,必须要实例化一个具体的类,这个类有特定的实现部分。例如,创建Xwindow对象会将Window抽象与XWindow的实现部分绑定起来,这使得客户程序依赖于XWindow的实现部分。这将使得很难将客户代码移植到其他平台上去。客户在创建窗口时应该不涉及到其具体实现部分。仅仅是窗口的实现部分依赖于应用运行的平台。这样客户代码在创建窗口时就不应涉及到特定的平台。Bridge模式解决以上问题的方法是,将Window抽象和它的实现部分分别放在独立的类层次结构中。其中一个类层次结构针对窗口接口(Window、IconWindow、TransientWindow),另外一个独立的类层次结构针对平台相关的窗口实现部分,这个类层次结构的根类为WindowImp。例如XwindowImp子类提供了一个基于XWindow系统的实现,如下图所示。对Window子类的所有操作都是用WindowImp接口中的抽象操作实现的。这就将窗口的抽象与系统平台相关的实现部分分离开来。因此,我们将Window与WindowImp之间的关系称之为桥接,因为它在抽象类与它的实现之间起到了桥梁作用,使它们可以独立地变化。4.适用性以下一些情况使用Bridge模式:•你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。•类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。•对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。•(C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。•正如在意图一节的第一个类图中所示的那样,有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh称这种类层次结构为“嵌套的普化”(nestedgeneralizations)。•你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。5.结构6.参与者•Abstraction(Window)—定义抽象类的接口。—维护一个指向Implementor类型对象的指针。•RefinedAbstraction(IconWindow)—扩充由Abstraction定义的接口。•Implementor(WindowImp)—定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。•ConcreteImplementor(XwindowImp,PMWindowImp)—实现Implementor接口并定义它的具体实现。7.协作•Abstraction将client的请求转发给它的Implementor对象。8.效果Bridge模式有以下一些优点:1)分离接口及其实现部分一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。2)提高可扩充性你可以独立地对Abstraction和Implementor层次结构进行扩充。3)实现细节对客户透明你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的引用计数机制(如果有的话)。9.实现使用Bridge模式时需要注意以下一些问题:1)仅有一个Implementor在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。这是Bridge模式的退化情况;在Abstraction与Implementor之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序时,模式的分离机制还是非常有用的——也就是说,不必重新编译它们,仅需重新连接即可。在C++中,Implementor类的类接口可以在一个私有的头文件中定义,这个文件不提供给客户。这样你就对客户彻底隐藏了一个类的实现部分。2)创建正确的Implementor对象当存在多个Implementor类的时候,你应该用何种方法,在何时何处确定创建哪一个Implementor类呢?如果Abstraction知道所有的ConcreteImplementor类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。例如,如果一个collection类支持多重实现,就可以根据collection的大小决定实例化哪一个类。链表的实现可以用于较小的collection类,而hash表则可用于较大的collection类。另外一种方法是首先选择一个缺省的实现,然后根据需要改变这个实现。例如,如果一个collection的大小超出了一定的阈值时,它将会切换它的实现,使之更适用于表目较多的collection。也可以代理给另一个对象,由它一次决定。在Window/WindowImp的例子中,我们可以引入一个factory对象(参见AbstractFactory),该对象的唯一职责就是封装系统平台的细节。这个对象知道应该为所用的平台创建何种类型的WindowImp对象;Window仅需向它请求一个WindowImp,而它会返回正确类型的WindowImp对象。这种方法的优点是Abstraction类不和任何一个Implementor类直接耦合。3)共享Implementor对象Coplien阐明了如何用C++中常用的Handle/Body方法在多个对象间共享一些实现。其中Body有一个对象引用计数器,Handle对它进行增减操作。将共享程序体赋给句柄的代码一般具有以下形式:4)采用多重继承机制在C++中可以使用多重继承机制将抽象接口和它的实现部分结合起来。例如,一个类可以用public方式继承Abstraction而以private方式继承ConcreteImplementor。但是由于这种方法依赖于静态继承,它将实现部分与接口固定不变的绑定在一起。因此不可能使用多重继承的方法实现真正的Bridge模式——至少用C++不行。10.代码示例下面的C++代码实现了意图一节中Window/WindwoImp的例子,其中Window类为客户应用程序定义了窗口抽象类:Window维护一个对WindowImp的引用,WindowImp抽象类定义了一个对底层窗口系统的接口。Window的子类定义了应用程序可能用到的不同类型的窗口,如应用窗口、图标、对话框临时窗口以及工具箱的移动面板等等。例如ApplicationWindow类将实现DrawContents操作以绘制它所存储的View实例:IconWindow中存储了它所显示的图标对应的位图名:并且实现DrawContents操作将这个位图绘制在窗口上:Window的操作由WindowImp的接口定义。例如,在调用WindowImp操作在