设计模式——设计模式选择——结构型

类对象结构型模式

Adapter(Wrapper)——适配器模式

意图

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作了。

动机

有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领
域所需要的接口不匹配。

适用性

以下情况使用Adapter模式

  • 你想使用一个已经存在的类,而它的接囗不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行
    子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

结构

设计模式——设计模式选择——结构型

参与者

  • Target
    定义Client使用的与特定领域相关的接口。
  • Client
    与符合Target接口的对象协同。
  • Adaptee
    定义一个己经存在的接口,这个接口需要适配。
  • Adapter
    对Adaptee的接口与Target接口进行适配

协作

Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求。

效果

类适配器和对象适配器

类适配器
  • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
  • 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
  • 仅仅引人了一个对象,并不需要额外的指针以间接得到adaptee。
对象适配器则
  • 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)一同时工作。Adapter也可以一次给所有的Adaptee添加功能。
  • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用
    这个子类而不是引用Adaptee本身。

使用Adapter模式时需要考虑的其他一些因素

Adapter的匹配程度

对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从单的接口转换(例如改变操作名)到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度。

可插入的Adapte

当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加人到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。

使用双向适配器提供透明搡作

使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容Adaptee的接口,因此并不是所有Adaptee对象可以被使用的
地方它都可以被使用。双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方
式查看同一个对象时,双向适配器尤其有用。

考虑一个双向适配器,双向适配器与两个被匹配的类都兼容,在这两个系统中它都可以工作。

实现

尽管Adapter模式的实现方式通常简单直接,但是仍需要注意以下一些问题:

使用c++实现适配器类

在使用c++实现适配器类时,Adapter类应该采用公共方式继承Target类,并且用私有方式继承Adaptee类。因此,Adapter类应该是Target的子类型,但不是Adaptee的子类型。

可插入的适配器

有许多方法可以实现可插入的适配器。对于它有三种实现方法:

首先(这也是所有这三种实现都要做的)是为Adaptee找到一个“窄”接口,即可用于适配的最小操作集。因为包含较少操作的窄接口相对包含较多操作的宽接口比较容易进行匹配。

使用抽象操作
在实现了Client和Target的类中定义窄Adaptee接口相应的抽象操作。这样就由子类来实现这些抽象操作并匹配具体的树结构的对象。
使用代理对象
Client将请求转发到代理对象,Client的客户进行一些选择,并将这个选择提供给代理对象。
参数化的适配器
用一个或多个对适配器进行参数化。模块构造支持无子类化的适配。一个模块可以匹配一个请求,并且适配器可以为每个请求存储一个模块。

相关模式

Bridge模式的结构与对象适配器类似,但是Bridge模式的出发点不同:Bridge目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而Adapter则意味着改变一个已有对象的接口。

Decorator模式增强了其他对象的功能而同时又不改变它的接口。因此decorator对应用程序的透明性比适配器要好。结果是decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。

proxy模式在不改变它的接口的条件下,为另一个对象定义了一个代理。

Bridge(Handle/Body)——桥接模式

意图

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

动机

当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但是此方法有时不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。

继承机制有两个不足之处:

  • 扩展Window抽象使之适用于不同种类的窗口或新的系统平台很不方便。
  • 继承机能使得客户代码与平台相关。每当客户创建一个窗口时,必须要实例化一个具体的类,这个类有特定的实现部分。

适用性

  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh称这种类层次结构为“嵌套的普化”(nestedgeneralizations)。
  • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。

结构

设计模式——设计模式选择——结构型

参与者

  • Abstraction
    定义抽象类的接口。
    维护一个指向Implementor类型对象的指针。
  • RefinedAbstraction
    扩充由Abstraction定义的接口。
  • Implementor
    定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。
  • Concretelmplementor
    实现Implementor接口并定义它的具体实现。

协作

Abstraction将client的请求转发给它的Implementor对象。

效果

Bridge模式有以下一些优点:

分离接口及其实现部分

一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。

将Abstraction与Implementor分离有种于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版车之间的二进制兼容性,一定要有这个性质。

另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。

提高可扩充性

可以独立地对Abstraction和Implementor层次结构进行扩充。

实现细节对客户透明

你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的引用计数机制(如果有的话)。

实现

使用Bridge模式时需要注意以下一些问题:

仅有一个Implementor

在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。这是Bridge模式的退化情况;在Abstraction与Implementor之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序时,模式的分离机制还是非常有用的一一一也就是说,不必重新编译它们,仅需重新连接即可。

创建正确的Implementor对象

当存在多个Implementor类的时候,你应该用何种方法在何时何处确定创建哪一个Implemen类呢?

如果Abstraction知道所有的Concretelmplementor类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。例如,如果一个collection类支持多重实现,就可以根据collection的大小决定实例化哪一个类。链表的实现可以用于较小的collection类,而hash表则可用于较大的collection类。

另外一种方法是首先选择一个缺省的实现,然后根据需要改变这个实现。例如,如果
一个collection的大小超出了一定的阈值时,它将会切换它的实现,使之更适用于表目较多的collection。

也可以代理给另一个对象,由它一次决定。在Window/WindowImp的例子中,我们可以
引人一个factory对象,该对象的唯一职责就是封装系统平台的细节。这个对象知道应该为所用的平台创建何种类型的Windowlmp对象;Window仅需向它请求一个Windowlmp,而它会返回正确类型的Windowlmp对象。这种方法的优点是Abstraction类不和任何一个Implementor类直接耦合。

共享Implementor对象

采用多重继承机制(C++)

相关模式

Abstract Factory模式可以用来创建和配置一个特定的Bridge模式。

Adapter模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

Composite——组合模式

意图

将对象组合成树形结构以表示“部分—整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

动机

描述了如何使用递归组合,使得用户不必对这些类进行区别。

适用性

以下情况使用Composite模式:

  • 你想表示对象的部分—整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

结构

设计模式——设计模式选择——结构型

参与者

  • Component
    为组合中的对象声明接口。
    在适当的情况下,实现所有类共有接口的缺省行为。
    声明一个接口用于访问和管理Component的子组件。
    (可选)在递妇结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
  • Leaf
    在组合中表示叶节点对象,叶节点没有子节点。
    在组合中定义图元对象的行为。
  • Composite
    定义有子部件的那些部件的行为。
    存储子部件。
    在Component接口中实现与子部件有关的操作
  • Client
    通过component接口操纵组合部件的对象。

协作

用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作。

效果

composite模式

定义了包含基本对象和组合对象的类层次结构

基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。

简化客户代码客户

可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。

使得更容易增加新类型的组件

新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。

使你的设计变得更加一般化

容易增加新组件也会产生一些同题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。

实现

我们在实现Composite模式时需要考虑以下几个问题:

显式的父部件引用

保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构的上移和组件的删除,同时父部件引用也支持Chain of Responsibility模式。

通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作。

对于父部件引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,而反之该组合以这些节点为子节点。保证这一点最容易的办法是,仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件。如果能在Composite类的Add和Remove操作中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式。

共享组件

共享组件是很有用的,比如它可以减少对存贮的需求。但是当一个组件只有一个父部件时,很难共享组件。
一个可行的解决办法是为子部件存贮多个父部件,但当一个请求在结构中向上传递时,这种方法会导致多义性。Flyweight模式讨论了如何修改设计以避免将父部件存贮在一起的方法。如果子部件可以将一些状态(或是所有的状态)存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。

最大化Component接口

Composite模式的目的之一是使得用户不知道他们正在使用的具体的Leaf和Composite类。为了达到这一目的,Composite类应为Leaf和Composite类尽可能多定义一些公共操作。Composite类通常为这些操作提供缺省的实现,而Leaf和Composite子类可以对它们进行重定义。

然而,这个目标有时可能会与类层次结构设计原则相冲突,该原则规定:一个类只能定义那些对它的子类有意义的操作。有许多Component所支持的操作对Leaf类似乎没有什么意义,那么Component怎样为它们提供一个缺省的操作呢?

有时一点创造性可以使得一个看起来仅对Composite才有意义的操作,将它移人Component类中,就会对所有的Component都适用。例如,访问子节点的接口是Composite类的一个基本组成部分,但对Leaf类来说并不必要。但是如果我们把一个Leaf看成一个没有子节点的Component,就可以为在Component类中定义一个缺省的操作,用于对子节点进行访问,这个缺省的操作不返回任何一个子节点。Leaf类可以使用缺省的实现,而Composite类则会重新实现这个操作以返回它们的子类。

声明管理子部件的操作

虽然composite类实现了Add和Remove操作用于管理子部件,但在Composite模式中一个重要的问题是:在Composite类层次结构中哪一些类声明这些操作。我们是应该在Component中声明这些操作,并使这些操作对Leaf类有意义呢,还是只应该在Composite和它的子类中声明并定义这些操作呢?
这需要在安全性和透明性之间做出权衡选择。

  • 在类层次结构的根部定义子节点管理接口的方法具有良好的透明性,因为你可以一致地使用所有的组件,但是这一方法是以安全性为代价的,因为客户有可能会做一些无意义的事情,例如在Leaf中增加和删除对象等。

  • 在Composite类中定义管理子部件的方法具有良好的安全性,因为在象c++这样的静态类型语言中,在编译时任何从Leaf中增加或删除对象的尝试都将被发现。但是这又损失了透明性,因为Leaf和Composite具有不同的接口。

Component是否应该实现一个Component列表

你可能希望在Component类中将子节点集合定义为一个实例变量,而这个Component类中也声明了一些操作对子节点进行访问和管理。但是在基类中存放子类指针,对叶节点来说会导致空间浪费,因为叶节点根本没有子节点。只有当该结构中子类数目相对较少时,才值得使用这种方法。

子部件排序

许多设计指定了Composite的子部件顺序。
如果需要考虑子节点的顺序时,必须仔细地设计对子节点的访问和管理接口,以便管理子节点序列。Iterator模式可以在这方面给予一些定的指导。

使用高速缓冲存贮改善性能

如果你需要对组合进行频繁的遍历或查找,Composite类可以缓冲存储对它的子节点进行遍历或查找的相关信息。Composite可以缓冲存储实际结果或者仅仅是一些用于缩短遍历或查询长度的信息。例如,动机一节的例子中Picture类能高速缓冲存贮其子部件的边界框,在绘图或选择期间,当子部件在当前窗口中不可见时,这个边界框使得Picture不需要再进行绘图或选择。

一个组件发生变化时,它的父部件原先缓冲存贮的信息也变得无效。在组件知道其父部件时,这种方法最为有效。因此,如果你使用高速缓冲存贮,你需要定义一个接口来通知组合组件它们所缓冲存贮的信息无效。

应该由谁删除Component

在没有垃圾回收机制的语言中,当一个Composite被销毁时,通常最好由Component负责删除其子节点。但有一种情况除外,即Leaf对象不会改变,因此可以被共享。

存贮组件最好用哪一种数据结构

Composite可使用多种数据结构存贮它们的子节点,包括连接列表、树、数组和hash表。数据结构的选择取决于效率。事实上,使用通用数据结构根本没有必要。有时对每个子节点,Composite都有一个变量与之对应,这就要求Composite的每个子类都要实现自己的管理接口。参见Interpreter模式中的例子。

相关模式

通常部件—父部件连接用于Chain of Responsibility模式。

Decorator模式经常与Composite模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有Add、Remove和GetChilld操作的Component接口。

Flyweight让你共享组件,但不再能引用他们的父部件。

Itertor可用来遍历Composite。

Visitor将本来应该分布在Compte和Leaf类中的操作和行为局部化。

Decorator(Wrapper)——装饰者模式

意图

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

动机

有时我们希望给某个对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框,或是一些行为,例如窗口滚动。

使用继承机制是添加功能的一种有效途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。

一种较为灵活的方式是将组件嵌人另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为装饰。

适用性

以下情况使用Decorator模式

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤消的职责。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

结构

设计模式——设计模式选择——结构型

参与者

  • Component
    定义一个对象接口,可以给这些对象动态地添加职责。
  • ConcreteComponent
    定义一个对象,可以给这个对象添加一些职责。
  • Decorator
    维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。
  • ConcreteDecorator
    向组件添加职责。

协作

Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。

效果

Decorator模式至少有两个主要优点和两个缺点:

比静态继承更灵活

与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。

使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错的。

避免在层次结构高层的类有太多的特征

Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decoratoro扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。

Decorator与它的Component不一样

Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。

有许多小对象

采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。

实现

使用Decorator模式时应注意以下几点:

接口的一致性

装饰对象的接口必须与它所装饰的Component的接口是一致的,因此,所有的ConcreteDecorator类必须有一个公共的父类(至少在C++中如此)。

省略抽象的Decorator类

当你仅需要添加一个职责时,没有必要定义抽象Decorator类。你常常需要处理现存的类层次结构而不是设计一个新系统,这时你可以把Decorator向Componen转发请求的职责合并ConcreteDecorator中。

保持Component类的简单性

为了保证接口的一致性,组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予component太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加。

改变对象外壳与改变对象内核

我们可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核。例如,strategy模式就是一个用于改变内核的很好的模式。

当Component类原本就很庞大时,使用Decorator模式代价太高,strategy模式相对更好一些。在Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。

相关模式

Adapter模式:Decorator模式不同于Adapter模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。

Composite模式:可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责一一它的目的不在于对象聚集。

Strategy模式:用一个装饰你可以改变对象的外表;而Strategy模式使得你可以改变对象的内核。这是改变对象的两种途径。

Facade——外观模式

意图

为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系饰更加容易使用。

动机

将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一是就是引人一个外观(facade)对象,它为子系统中较一般的设施提供了一个单一而简单的界面。

适用性

在遇到以下情况使用Facade模式:

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引人facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的人口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。

结构

设计模式——设计模式选择——结构型

参与者

  • Facade
    知道哪些子系统类负责处理请求。
    将客户的请求代理给适当的子系统对象。
  • Subsystemclasses
    实现子系统的功能。
    处理由Facade对象指派的任务。
    没有facade的任何相关信息;即没有指向facade的指针。

协作

客户程序通过发送请求给Facade的方式与子系统通讯,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它
的接口转换成子系统的接口。

使用Facade的客户程序不需要直接访问子系统对象。

效果

Facade模式有下面一些优点、
优点一
它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便
优点二
它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。

在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用Facade可以降低编译依赖性,限制重要系统中较小的变化所需的重编译工作。Facade模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。
优点三
如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。

实现

使用Facade模式时需要注意以下几点:

降低客户—子系统之间的耦合度

用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。这样,客户就可以通过抽象的Facade类接口与子系统通讯。这种抽象合关系使得客户不知道它使用的是子系统的哪一个实现。

除生成子类的方法以外,另一种方法是用不同的子系统对象配置Facade。对象。为定制Facade,仅需对它的子系统对象(一个或多个)进行替换即可。

公共子系统类与私有子系统类

一个子系统与一个类的相似之处是,它们都有接口并且它们都封装了一些东西——类封装了状态和操作,而子系统封装了一类。考虑一个类的公共和私有接囗是有益的,我们也可以考虑子系统的公共和私有接口。

子系统的公共接口包含所有的客户程序可以访问的类;私有接口仅用于对子系统进行扩充。当然,Facade类是公共接口的一部分,但它不是唯一的部分,子系统的其他部分通常也是公共的。例如,编译子系统中的Parser类和Scanner类就是公共接口的一部分。

相关模式

Abstract Factory模式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。

Mediator模式与Facade模式的相似之处是,它抽象了一些己有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在。

通常来讲,仅需要一个Facade对象,因此Fac黿e对象通常属于Singleton模式。

Flyweight——享元模式

意图

运用共享技术有效地支持大量细粒度的对象。

动机

有些应用程序得益于在其整个设计过程中采用对象技术,但简单化的实现代价极大。
例如,大多数文档辑器的实现都有文本格式化和编辑功能,这些功能在一定程度上是模块化的。面向对象的文档编辑器通常使用对象来表示嵌人的成分,例如表格和图形。尽管用对象来表示文档中的每个字符会极大地提高应用程序的灵活性,但是这些编辑器通常并不这样做。字符和嵌入成分可以在绘制和格式化时统一处理,从而在不影响其他功能的情况下能对应用程序进行扩展,支持新的字符集。应用程序的对象结构可以模拟文档的物理结构。

适用性

Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式:

  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量的对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。

结构

设计模式——设计模式选择——结构型

参与者

  • Flyweight
    描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。
  • ConcreteFlyweight
    实现flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景。
  • UnsharedConcreteFlyweight
    并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlywerght对象作为子节点。
  • FlyweightFactory
    创建并管理flyweight对象。
    确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
  • Client
    维持一个对flyweight的引用。
    计算或存储一个(多个)flywerght的外部状态。

协作

  • flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweigh树象的操作时,将该状态传递给它。
  • 用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。

效果

使用Flyweight模式时,传输、查找和/或计算外部状态都会产生运行时的开销,尤其当flyweight原先被存储为内部状态时。然而,空间上的节省抵消了这些开销。共享的flyweight越多,空间节省也就越大。

存储节约由以下几个因素决定:

  • 因为共享,实例总数减少的数目
  • 对象内部状态的平均数目
  • 外部状态是计算的还是存储的

共享的Flyweight越多,存储节约也就越多。节约量随着共享状态的增多而增大。当对象使用大量的内部及外部状态,并且外部状态是计算出来的而非存储的时候,节约量将达到最大。所以,可以用两种方法来节约存储:用共享减少内部状态的消耗,用计算时间换取对外部状态的存储。

Flyweight模式经常和Composite模式结合起来表示一个层次式结构,这一层次式结构是一个共享叶节点的图。共享的结果是,Flyweight的叶节点不能存储指向父节点的指针。而父节点的指针将传给Flyweight作为它的外部状态的一部分。这对于该层次结构中对象之间相互通讯的方式将产生很大的影响。

实现

在实现Flyweight模式时,注意以下几点:

删除外部状态

该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。如果不同种类的外部状态和共享前对象的数目相同的话,删除外部状态不会降低存储消耗。理想的状况是,外部状态可以由一个单独的对象结构计算得到,且该结构的存储要求非常小。

例如,在我们的文档编辑器中,我们可以用一个单独的结构存储排版布局信息,而不是存储每一个字符对象的字体和类型信息,布局图保持了带有相同排版信息的字符的运行轨迹。当某字符绘制自己的时候,作为绘图遍历的副作用它接收排版信息。因为通常文档使用的字体和类型数量有限,将该信息作为外部信息来存储,要比内部存储高效得多。

管理共享对象

因为对象是共享的,用户不能直接对它进行实例化,因此Flyweight Factory可以帮助用户查找某个特定的Flyweight对象。Flyweight Factory对象经常使用关联存储帮助用户查找感兴趣的Flyweight对象。例如,在这个文档编辑器一例中的Flyweight工厂就有一个以字符代码为索引的Flyweight表。管理程序根据所给的代码返回相应的Flyweight,若不存在,
则创建一个Flyweight。

共享还意味着某种形式的引用计数和垃圾回收,这样当一个Flyweight不再使用时,可以回收它的存储空间。然而,当Flyweight的数目固定而且很小的时候(例如,用于ACSII码的Flyweight),这两种操作都不必要。在这种情况下,Flyweight完全可以永久保存。

相关模式

Flyweight模式通常和Composite模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。

通常,最好用Flyweight实现State和strategy对象。

Proxy(Surrogate)——代理模式

意图

为其他对象提供一种代理以控制对这个对象的访问。

动机

对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。我们考虑一个可以在文档中嵌人图形对象的文档编辑器。有些图形对象(如大型光栅图像)的创建开销很大。但是打开文档必须很迅速,因此我们在打开文档时应免一次性创建所有开销很大的对象。因为并非所有这些对象在文档中都同时可见,所以也没有必要同时创建这些对象。

这一限制条件意味着,对于每一个开销很大的对象,应该根据需要进行创建,当一个图像变为可见时会产生这样的需要。但是在文档中我们用什么来代替这个图像呢?我们又如何才能隐藏根据需要创建图像这一事实,从而不会使得编辑器的实现复杂化呢?例如,这种优化不应影响绘制和格式化的代码。

问题的解决方案是使用另一个对象,即图像Proxy替代那个真正的像。proxy可以代替一个图像对象,并且在需要时负责实例化这个图像对象。

只有当文档编辑器**图像代理的Draw操作以显示这个图像的时候,图像proxy才创建真正的图像。Proxy直接将随后的请求转发给这个图像对象。因此在创建这个图像以后,它心须有一个指向这个图像的引用。

我们假设图像存储在一个独立的文件中。这样我们可以把文件名作为对实际对象的引用。Proxy还存储了图像的尺寸(extent),即它的长和宽。有了图像尺寸,proxy无须真正实例化这个图像就可以响应格式化程序对图像尺寸的请求。

适用性

在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Pxy模式。下面是一些可以使用Proxy模式常见情况:

  1. 远程代理(Remote Proxy) 为一个对象在不同的地址空间提供局部代表。
  2. 虚代理(Virtualy Proxy) 根据需要创建开销很大的对象。在动机一节描述的ImageProxy就是这样一种代理的例子。
  3. 保护代理(Protection Proxy) 控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  4. 智能指引(SmartReference) 取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
    1. 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。
    2. 当第一次引用一个持久对象时,将它装人内存。
    3. 在访问一个实际对象前,检查是否己经锁定了它,以确保其他对象不能改变它。

结构

设计模式——设计模式选择——结构型

参与者

  • Proxy
    保存一个引用使得代理可以访问实体。若RealSubject和subject的接口相同,Proxy会引用Subjecte。
    提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
    控制对实体的存取,并可能负责创建和删除它。
    其他功能依赖于代理的类型:
    • Remote Proxy 负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
    • Virtualy Proxy 可以缓存实体的附加信息,以便延迟对它的访问。例如,动机一节中提到的ImageProxy缓存了图像实体的尺寸。
    • Protection Proxy 检查调用者是否具有实现一个请求所必需的访问权限。
  • Subject
    定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
  • RealSubject
    定义Proxy所代表的实体。

协作

代理根据其种类在适当的时候向RealSubject转发请求。

效果

proxy模式在访问对象时引人了一定程度的间接性。根据代理的类型,附加的间接性有多
种用途:

  1. Remote proxy可以隐藏一个对象存在于不同地址空间的事实。
  2. Virtual Proxy可以进行最优化,例如根据要求创建对象。
  3. Protection Proxies和Smart Reference都允许在访问一个对象时有一些附加的内务处理(Housekeeping Task)。

proxy模式还可以对用户险藏另一种称之为copy-on-write的优化方式,该优化与根据需要创建对象有关。拷贝一个庞大而复杂的对象是一种开销很大的操作,如果这个拷贝根本没有被修改,那么这些开销就没有必要。用代理延迟这一拷贝过程,我们可以保证只有当这个对象被修改的时候才对它进行拷贝。

在实现copy-on-write时必须对实体进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求个修改该实体的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减少实体的引用计数。当引用的数目为零时,这个实体将被删除。
Copy-on-Wnte可以大幅度的降低拷贝庞大实体时的开销力

实现

Proxy并不总是需要知道实体的类型

若proxy类能够完全通过一个抽象接口处理它的实体,则无须为每一个RealSubject类都生成一个Proxy类;Proxy可以统一处理所有的RealSubject类。但是如果Proxy要实例化RealSubjects(例如在virtualproxy中),那么它们必须知道具体的类。

另一个实现方面的问题涉及到在实例化实体以前怎样引用它。有些代理必须引用它们的实体,无论它是在硬盘上还是在内存中。这意味着它们必须使用某种独立于地址空间的对象标识符。在目的一节中,我们采用一个文件名来实现这种对象标识符。

待补充

相关模式

Adapter:适配器Adapter为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。

Decorator:尽管Decorator的实现部分与代理相似,但Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。

代理的实现与Decorator的实现类似,但是在相似的程度上有所差别。Protection Proxy的实现可能与Decorator的实现差不多。另一方面,Remote Proxy不包含对实体的直接引用,而只是一个间接引用,如“主机ID,主机上的局部地址。”Virtual Proxy开始的时候使用一个间接引用,例如一个文件名,但最终将获取并使用一个直接引用。

结构型模式的讨论

你可能已经注意到了结构型模式之间的相似性,尤其是它们的参与者和协作之间的相似性。这可能是因为结构型模式依赖于同一个很小的语言机制集合构造代码和对象:单继承和多重继承机制用于基于类的模式,而对象组合机制用于对象式模式。但是这些相似性掩盖了这些模式的不同意图。在本节中,我们将对比这些结构型模式,使你对它们各自的优点有所了解。

Adapter与Bridge

共同特征

都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。
都涉及到从自身以外的一个接口向这个对象转发请求。

解决什么问题

Adapter

Adapter模式主要是为了解决两个已有接口之间不匹配的问题。它不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化。这种方式不需要对两个独立设计的类中的任一个进行重新设计,就能够使它们协同工作。

Bridge

Bridge模式则对抽象接口与它的(可能是多个)实现部分进行桥接。虽然这一模式允许你修改实现它的类,它仍然为用户提供了一个稳定的接口。Bridge模式也会在系统演化时适应新的实现。

用于软件生命周期的哪个阶段

Adapter模式在类已经设计好后实施

两个不兼容的类必须同时工作时,就有必要使用Adapter模式,其目的一般是为了避免代码重复。此处耦合不可预见。

Bridge模式在设计类之前实施

Bridge的使用者必须事先知道:“一个抽象将有多个实现部分,并且抽象和实现两者是独立演化的。

Facade

你可能认为facade是另外一组对象的适配器。但这种解释忽视了一个事实:即Facade定义一个新的接口,而Adapter则复用一个原有的接口。记住,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。

Composite、Decorator与Proxy

Composite与Decorator

Composite模式和Decorator模式具有类似的结构图,这说明它们都基于递归组合来组织可变数目的对象。这一共同点可能会使你认为,decorator对象是一个退化的composite,但这一观点没有领会Decorator模式要点。相似点仅止于递归组合,同样,这是因为这两个模式的目的不同。

不同

Decorator旨在使你能够不需要生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。

Composite则有不同的目的,它旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理。它重点不在于修饰,而在于表示。

通常协同使用

尽管它们的目的截然不同,但却具有互补性。因此Composite和Decorator模式通常协同使用。在使用这两种模式进行设计时,我们无需定义新的类,仅需将一些对象插接在一起即可构建应用。这时系统中将会有一个抽象类,它有一些composite子类和decorator子类,还有一些实现系统的基本构建模块。此时,composites和decorator将拥有共同的接口。从
Decorator模式的角度看,composite是一个ConcreteComponent。而从composite模式的角度看,decorator则是一个Leaf。当然,他们不一定要同时使用,正如我们所见,它们的目的有很大的差别。

Decorator与proxy

另一种与Decorator模式结构相似的模式是Proxy。这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy和decorator对象的实现部分都保留了指向另一个对象的指针,它们向这个对象发送请求。然而同样,它们具有不同的设计目的。

不同

像Decorator模式一样,Proxy模式构成一个对象并为用户提供一致的接口。但与Decorator模式不同的是,Proxy模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。

在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为Decorator模式中一个必不可少的部分。而在Proxy模式中则不是这样,因为Proxy模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达。

模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。但这并不意味着这些模式不能结合使用。可以设想有一个proxy—decorator,它可以给proxy添加功能,或是一个decorator—proxy用来修饰一个远程对象。尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。