设计模式:可复用面向对象软件的基础

本网站用的阿里云ECS,推荐大家用。自己搞个学习研究也不错

设计模式:可复用面向对象软件的基础

前言

设计模式的书我听闻比较经典的有《设计模式:可复用面向对象软件的基础》、《Head first 设计模式》、《设计模式之禅》、《大话设计模式》、《Python编程实战:运用设计模式、并发和程序库创建高质量程序》。
前两本都看过,看的时候感觉都貌似理解了,当时也能在工作中运用一下。不过过个三五个月就发会现,这些知识又丢了。所以打算搞个便于记忆的摘要出来。没事拿出来翻翻下,勾起下记忆。主要是结构图,适应性,效果。基本上看结构图就能理解了。

我建议大家反复阅读《设计模式:可复用面向对象软件的基础》。其他书适合初学者阅读。

感觉这些模式在客户端开发中体现的比较多。在Web开发中较少。
拿Android来说,
View和ViewGroup其实就是composite模式。
在View和ViewGroup中的事件传递,就是CHAIN OF RESPONSIBILITY。
而Observer更是有现在的接口observer和observable

设计模式有4个基本要素

  1. 模式名称
  2. 问题:描述了应该在何时使用模式。
  3. 解决方案:描述了设计的组成部分,它们之间的相互关系及各自的职责和协作方式。
  4. 效果:描述了模式应用的效果及使用模式应权衡的问题。

23个设计模式

  1. Abstract Factory(抽象工厂):提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  2. Adapter(适配器):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  3. Bridge(桥接):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  4. Builder(生成器):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  5. Chain of Responsibility(职责链):为解除请求的发送者和接收者之间耦合,而使得多个对象都有机会处理这个对象。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
  6. Command(命令):将一个请求封闭为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可取消的操作。
  7. Composite(组合):将对象组合成树形结构以表示”部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。
  8. Decorator(装饰器):动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活。
  9. Facade(外观):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更容易使用。
  10. Factory Method(工厂方法):定义一个用于创建对象的接口,让子类决定让哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
  11. Flyweight(享元):运用共享技术有效地支持大量细粒度的对象。
  12. Interpreter(解释器):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
  13. Iterator(迭代器):提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。
  14. Mediator(中介者):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  15. Memento(备忘):在不破坏封闭性的前提下,捕获一个对象的内部状态,并在该对象之外保存状态。这样以后就可以将该对象恢复到保存的状态。
  16. Observer(观察者):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
  17. Prototype(原型):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
  18. Proxy(代理):为其他对象提供一个代理以控制对这个对象的访问。
  19. Singleton(单例):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  20. State(状态):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
  21. Strategy(策略):定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
  22. Template Method(模板方法):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  23. Visitor(访问者):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

设计模式分类

我们根据两条准则(表1-1)对模式进行分 类。
第一是目的准则,即模式是用来完成什么工作的。模式依据其目的可分为创建型(Creational)、结构型(Structural)、或行为型(Behavioral)三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。
第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。从某种意义上来说,几乎所有模式 都使用继承机制,所以”类模式”只指那些集中于处理类间关系的模式,而大部分模式都属 于对象模式的范畴。
创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。结构型类模式使用继承机制来组合类,而结构型对象模式则描述了对象的组装方式。行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作以完成单个对象所无法完成的任务

导致重新设计的一般原因,以及解决这些问题的设计模式举例

  1. 通过显式地指定一个类来创建对象 在创建对象时指定类名将使你受特定实现的约束 而不是特定接口的约束。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。
    设计模式: AbstractFactory , FactoryMethod , Prototype 。
  2. 对特殊操作的依赖 当你为请求指定一个特殊的操作时,完成该请求的方式就固定下 来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方 法。
    设计模式: ChainofResposibility , Command。
  3. 对硬件和软件平台的依赖外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平 台的更新。所以设计系统时限制其平台相关性就很重要了。
    设计模式: AbstractFactory , Bridge。
  4. 对对象表示或实现的依赖 知道对象怎样表示、保存、定位或实现的客户在对象发生 变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。
    设计模式: AbstractFactory , Bridge , Memento, Proxy
  5. 算法依赖 算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。
    设计模式: Builder, Iterator, Strategy , TemplateMethod , Visitor
  6. 紧耦合 紧耦合的类很难独立地被复用,因为它们是互相依赖的。紧耦合产生单块的 系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、 移植和维护的密集体。松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。 设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。
    设计模式: AbstractFactory , Command , Facade , Mediator , Observer ,Chainof Responsibility。
  7. 通过生成子类来扩充功能 通常很难通过定义子类来定制对象。每一个新类都有固定 的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。如,重定义一个操 作可能需要重定义其他操作。一个被重定义的操作可能需要调用继承下来的操作。并且子类 方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。一般的对象组合技术和具体的委托技术,是继承之外组合对象行为的另一种灵活方法。 新的功能可以通过以新的方式组合已有对象,而不是通过定义已存在类的子类的方式加到应 用中去。另一方面,过多使用对象组合会使设计难于理解。许多设计模式产生的设计中,你 可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。
    设计模式: Bridge, ChainofResponsibility ,Composite , Decorator , Observer , Str ategy 。
  8. 不能方便地对类进行修改 有时你不得不改变一个难以修改的类。也许你需要源代码 而又没有 (对于商业类库就有这种情况 ),或者可能对类的任何改变会要求修改许多已存在的其 他子类。设计模式提供在这些情况下对类进行修改的方法。
    设计模式: Adapter , Decorator , Visitor 。
    #设计模式所支持的设计的可变方面

设计模式背后的6大设计原则

  1. 单一职责原则
  2. 里氏替换原则
  3. 依赖倒置原则
  4. 接口隔离原则
  5. 迪米特原则
  6. 开闭原则

创建型模式

创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那 些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委 托给另一个对象。

在这些模式中有两个不断出现的主旋律。第一,它们都将关于该系统使用哪些具体的类 的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起的。

ABSTRACT FACTORY(抽象工厂)–对象创建型模式

  1. 意图
    提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  2. 别名
    Kit
  3. 动机 略
  4. 适用性
    在以下情况下可以使用Abstract Factory模式

    • 一个系统要独立于它的产品的创建、组合和表示时。
    • 一个系统要由多个产品系列中的一个来配置时。
    • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
    • 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
  5. 结构
  6. 参与者 略
  7. 协作
    • 通常在运行时刻创建一个ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂。
    • AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类。
  8. 效果
    AbstractFactory模式有下面的一些优点和缺点:

    1. 它分离了具体的类 Abstract Factory模式帮助你控制一个应用创建的对象的类。因为
      一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。
    2. 它使得易于交换产品系列 一个具体工厂类在一个应用中仅出现一次— 即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。
    3. 它有利于产品的一致性 当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要。而 AbstractFactory 很容易实现这一点。
    4. 难以支持新种类的产品 难以扩展抽象工厂以生产新种类的产品。这是因为
      AbstractFactory接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口,这将涉及A bstractFactory类及其所有子类的改变 。
  9. 实现 略

BUILDER(生成器)–对象创建型模式

  1. 意图 将一个复杂对象的构建与它的表示分离,使得现样的构建过程可以创建不同的表示。
  2. 动机 略
  3. 适应性
    在以下情况使用Builder模式

    • 在创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
    • 当构造过程必须允许被构造的对象有不同的表示时。
  4. 结构
  5. 参与者 略
  6. 协作
    • 客户创建Director对象,并用它所想要的Builder对象进行配置。
    • 一旦产品部件被生成,导向器就会通知生成器。
    • 生成器处理导向器的请求,并将部件添加到该产品中。
    • 客户从生成品中检索产品。

  1. 效果
    这里是Builder模式的主要效果:

    1. 它使你可以改变一个产品的内部表示。Builder对象提供给导向器一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构。它同时也隐藏了该产品是如何装配的。因为产品是通过抽象接口构造的,你在改变该产品的内部表示时所要做的只是定义一个新的生成器。
    2. 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息;这些类是不出现在Builder接口中的。每个Con creteBuilder包含了创建和装配一个特定产品的所有代码。 这些代码只需要写一次;然后不同的Dir ector可以复用它以在相同部件集合的基础上构作不同的Product。
    3. 它使你可对构造过程进行更精细的控制。Builder 模式 与 一下子就生成产品的创建型模式不同,它是在导向者的控制下一步一步构造产品的。仅当该产品完成时导向者才从生成器中取回它。因此Builder接 口相比其他创建型模式能更好的反映产品的构造过程。 这使你可以更精细的控制构建过程,从而能更精细的控制所得产品的内部结构。
  2. 实现 略
  3. 代码示例 略
  4. 已知应用 略
  5. 相关模式
    Abstract Factory与Builder相似,因为它也可以创建复杂对象。主要的区别是Builder模式着重于一步步构造一个复杂对象。而Abstract Factory着重于多个系列的产品对象(简单的或是复杂的)。Builder在最后的一步返回产品,而对于Abstract Factory来说,产品是立即返回的。
    Composite通常是用Builder生成的。

FACTORY METHOD(工厂方法) –对象创建型模式

  1. 意图 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
  2. 别名 虚构造器(Virtual Constructor)
  3. 动机 框架使用抽象类定义和维护对象之间的关系。这些对象的创建通常也由框架负责。
  4. 适用性
    在下列情况下可以使用Factory Method模式:

    1. 当一个类不知道它所必须创建的对象的类的时候。
    2. 当一个类希望由它的子类来指定它所创建的对象的时候。
    3. 当类将创建对象的职责委托给多个帮助子类中的一某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
  5. 结构
  6. 参与者
    • Product –定义工厂方法所创建的对象的接口
    • ContreteProduct–实现Product
    • Creator–声明工厂方法,该方法返回一个Product类型的对象;可以调用工厂方法以创建一个Product对象。
    • ConcreteCreator–重定义工厂方法以返回一个ConcreteProduct实例。
  7. 协作 Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct实例。
  8. 效果
    工厂方法不再将与特定应用有关的类绑定到你的代码中。代码仅处理Product接口;因此它可以与用户定义的任何ConcreteProduct类一起使用。
    工厂方法的一个潜在缺点在于客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类。
    下面是Factory Method模式的另外两种效果:

    1. 为子类提供挂钩
    2. 连接平行的类层次。如
  9. 实现
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Abstract Factory经常用工厂方法来实现。Abstract Factory模式中动机一节的例子也对Factory Method进行了说明。
    工厂方法通常在Template Methods中被调用。
    Prototypes不需要创建Creator的子类。但是,它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象。而Factory Method不需要这样的操作。

PROTOTYPE(原型)–对象创建型模式

  1. 意图 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  2. 动机 略
  3. 适应性
    当一个系统应该独立于它的产品创建、构成和表示时,要使用Prototype模式;以及

    • 当要实例化的类是在运行时刻指定的,例如,通过动态装载;或者
    • 为了避免创建一个与产品类层次平行的工厂类层次时;或者
    • 当一个类的实例只能有几个不同的状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
  4. 结构
  5. 参与者
  • prototype — 声明一个克隆自身的接口。
  • ConcretePrototype — 实现一个克隆自身的操作。
  • Client — 让一个原型克隆自身从而创建一个新的对象。
  1. 协作
    客户请求一个原型克隆自身
  2. 效果
    Prototype有许多和Abstract Factory和Builder一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
    下面列出Prototype模式的另外一些优点。

    1. 运行时刻增加和删除产品
    2. 改变值以指定新对象
    3. 改变结构以指定新对象
    4. 减少子类的构造
    5. 用类动态配置应用
  3. 实现 略
  4. 代码示例 略
  5. 已知应用 略
  6. 相关模式
    Prototype和Abstract Factory模式在某种方面是相互竞争的。但是它们也可以一起使用。Abstract Factory可以存储一个被克隆的原型的集合,并且返回产品对象。
    大量使用Composite和Decorator模式的设计通常也可从Prototype模式处获益。

SINGLETON(单件) — 对象创建型模式

  1. 意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  2. 动机 略
  3. 适用性
    在下面的情况下可以使用Singleton模式

    • 当类只能有一个实例并且客户可以从一个众所周知的访问点访问它时。
    • 当这个唯一实例应该是通过子类化扩展时,并且客户应该无需更改代码就能使用一个扩展的实例时。
  4. 结构
  5. 参与者
    • Singleton –定义一个Instance操作,允许客户访问它的唯一实例;可能负责创建它自己的唯一实例。
  6. 协作
    客户只能通过Singleton的Instance操作访问一个Singleton的实例。
  7. 效果
    Singleton模式有许多优点:

    1. 对唯一实例的受控访问
    2. 缩小名空间
    3. 允许对操作和表示的精化
    4. 允许可变数目的实例
    5. 比类操作更灵活
  8. 实现 略
  9. 代码示例 略
  10. 已知应用 略
  11. 相关模式
    很多模式可以使用Singleton模式实现。

结构型模式

结构型模式涉及到如何组合类和对象以获得更大的结构。
结构型类模式采用继承机制来 组合接口或实现。
结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从 而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有 更大的灵活性,而这种机制用静态类组合是不可能实现的。

ADAPTER(适配器)–类对象结构型模式

  1. 意图 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  2. 别名 包装器Wrapper
  3. 动机 有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配。
  4. 适用性
    以下情况使用Adapter模式

    • 你想使用一个已经存在的类,而它的接口不符合你的需求。
    • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。
    • (仅适用于对象Adapter)你想使用一些已经存在的类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
  5. 结构
  6. 参与者 略
  7. 协作 Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求。
  8. 效果
    类适配器和对象适配器有不同的权衡。类适配器

    • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
    • 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
    • 仅仅引入了一个对象,并不需要额外的指针以间接得到adaptee。

    对象适配器则:

    • 允许一个Adapter与多个Adaptee–即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。
    • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    模式Bridge的结构与对象适配器类似,但是Bridge模式的出发点不同:Bridge目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而Adapter则意味着改变一个已有对象的接口。
    Decorator模式增加了其他对象的功能而同时又不改变它的接口。因此decorator对应用程序的透明性比适配器好。结果是decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。
    模式Proxy在不改变它的接口的条件下,为另一个对象定义了一个代理。

BRIDGE(桥接)–对象结构型模式

  1. 意图 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  2. 别名 Handle/Body
  3. 动机 略
  4. 适用性
    以下一些情况使用Bridge模式:

    • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
    • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时 Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
    • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
    • ( C + +)你想对客户完全隐藏抽象的实现部分。在 C + + 中 , 类 的 表 示 在 类 接 口 中 是 可 见的。
    • 正如在意图一节的第一个类图中所示的那样,有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。 Rumbaugh称这种类层次结构为“嵌套的普化” (nested generalizations)。
    • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。 一个简单的例子便是 C oplien的 String 类 [Cop92], 在这个类中多个对象可以共享同一个字符串表示(StringRep)。
  5. 结构
  6. 参与者 略
  7. 协作 Abstraction将client的请求转发给它的Implementor对象。
  8. 效果
    Bridge 模式有以下一些优点:

    • 分离接口及其实现部分 一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将 Abstraction 与 Implementor 分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 Abstraction 类和它的客户程序 。 为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需 知道 Abstraction 和 Implementor 即可。
    • 提高可扩充性 你可以独立地对 Abstraction和 Implementor 层次结构进行扩充。
    • 实现细节对客户透明 你可以对客户隐藏实现细节, 例如共享Implementor对象以及相应的引用计数机制(如果有的话)。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Abstract Factory模式可以用来创建和配置一个特定的Bridge模式。
    Adapter模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然后,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

COMPOSITE(组合)–对象结构型模式

  1. 意图 将对象组成合树形结构以表示”部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
  2. 动机 略
  3. 适用性
    以下情况使用Composite模式:
  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
  1. 结构
  2. 参与者 略
  3. 协作
    用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。
    如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作。
  4. 效果
    Composite模式

    • 定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
    • 简化客户代码 客户可以一致地使用组合结构和单个对象。通常用户不知道 (也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码 , 因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
    • 使得更容易增加新类型的组件新定义的 Composite 或 Leaf 子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的 Component 类而改变。
    • 使你的设计变得更加一般化 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用 Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
  5. 实现 略
  6. 代码示例 略
  7. 已知应用 略
  8. 相关模式
    通常部件-父部件连接用于Responsibility of Chain模式。
    Decorator 模式经常与 Composite 模式一起使用。 当装饰和组合一起使用时, 它们通常有一个公共的 父类。 因此装饰必须支持具有Add、Remove 和 GetChild 操作的 Component

Flyweight 让你共享组件,但不再能引用他们的父部件。
Iterator可用来遍历 Composite。
Visitor 将本来应该分布在 Composite 和 Leaf类中的操作和行为局部化。

DECORATOR(装饰)–对象结构型模式

  1. 意图 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
  2. 别名 包装器Wrapper
  3. 动机 有时我们希望给某个对象而不是整个类添加一些功能。
  4. 适用性
    以下情况使用Decorator模式

    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
    • 处理那些可以撤消的职责。
    • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
  5. 结构
  6. 参与者 略
  7. 协作 Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。
  8. 效果
    Decorator模式至少有两个主要优点和两个缺点:

    1. 比静态继承更灵活
    2. 避免在层次结构高层的类有太多的特征
    3. Decorator与它的Component不一样 使用装饰时不应该依赖对象标识。
    4. 有许多小对象 系统难学习,排错困难。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用
  12. 相关模式
    Adapter模式:Decorator模式不同于Adapter模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。
    Composite模式:可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责–它的目的不在于对象聚集。
    Strategy模式:用一个装饰你可以改变对象的外表;而Strategy模式使得你可以改变对象的内核。这是改变对象的两种途径。

FACADE(外观)–对象结构型模式

  1. 意图
    为子系统中的一组接口提供一个一致的界面, F a c a d e 模 式 定 义 了 一 个 高 层 接 口 , 这 个 接口使得这一子系统更加容易使用。
  2. 动机
    将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使 子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一是就是引入一个 外观
    ( f a c a d) e 对 象 , 它 为 子 系 统 中 较 一 般 的 设 施 提 供 了 一 个 单 一 而 简 单 的 界 面 。
  3. 适用性
    在遇到以下情况使用 F a c a d e 模式

    • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容 易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。 Facade可以提供一个简单的缺省视图, 这一视图对 大多数用户来说已经足够 , 而那些需要更多的可定制性的用户可以越过 f a c a d e层。
    • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
    • 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化它们之间的依赖关系
  4. 结构
  5. 参与者 略
  6. 协作
    • 客户程序通过发送请求给Facade的方式与子系统通讯,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
    • 使用Facade的客户程序不需要直接访问子系统对象。
  7. 效果
    Facade 模式有下面一些优点:

    1. 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
    2. 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。 松耦合关系使得子系统的组件变化不会影响到它的客户。 Facade 模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。 Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译 工作以节省间用Facade可以降低编译依赖性, 限制重要系统中较小的变化所需的重编译工作。Facade 模式同样也有利于简化系统在不同平台之间的移植过程, 因为编译一个子系统一般不需要编译所有其他的子系统。
    3. 如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之 间加以选择。
  8. 实现 略
  9. 代码示例 略
  10. 已知应用 略
  11. 相关模式
    Abstract Factory模式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。
    Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道facade的存在。
    通常来说,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。

FLYWEIGHT(享元)–对象结构型模式

  1. 意图
    运用共享技术有效地支持大量细粒度的对象。
  2. 动机 有些应用程序得益于在其整个设计过程中采用对象技术,但简单化的实现代价极大。
  3. 适用性
    Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。 当以下情况都成立时使用Flyweight模式:

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

    下面的对象图说明了如何共享flyweight。
  5. 参与者
    • Flyweight
      • 描述一个接口,通过这个接口 flyweight可以接受并作用于外部状态。
    • ConcreteFlyweight
      • 实现 Flyweight 接口,并为内部状态(如果有的话)增加存储空间。 ConcreteFlyweight对象必 须是可共享的 。 它所存储的状态必须是内部的 ; 即 , 它必须独立于 ConcreteFlyweight 对象的场景。
    • UnsharedConcreteFlyweight
      • 并非所有的 Flyweight 子类都需要被共享。 Flyweight 接口使共享成为可能 , 但它并不强制共享。在Flyweight 对象结构的某些层次, UnsharedConcreteFlyweight对象通常将Concre teFlyweight 对象作为子节点
    • FlyweightFactory
      • 创建并管理 flyweight 对象。
      • 确保合理地共享 flyweight。 当用户请求一个flyweight时, FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
    • Client
      • 维持一个对 flyweight 的引用。
      • 计算或存储一个(多个) flyweight 的外部状态。
  6. 协作
    • flyweight 执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight 对象之中;而外部对象则由 Client 对象存储或计算。当用户调用 flyweight 对象的操作时,将该状态传递给它。
    • 用户不应直接对 ConcreteFlyweight 类进行实例化,而只能从 FlyweightFactory对象得到Conc reteFlyweight对象,这可以保证对它们适当地进行共享。
  7. 效果
    使用Flyweight模式时, 传输、查找和/或计算外部状态都会产生运行时的开销, 尤其当flyweight原先被存储为内部状态时。然而,空间上的节省抵消了这些开销。共享的flyweight越多,空间节省也就越大。
    存储节约由以下几个因素决定:

    • 因为共享,实例总数减少的数目
    • 对象内部状态的平均数目
    • 外部状态是计算的还是存储的
  8. 实现 略
  9. 代码示例 略
  10. 已知应用 略
  11. 相关模式
    Flyweight模式通常和Composite模式结合起来,用共享结点的有向无环图实现一个逻辑上的层次结构。
    通常,最好用Flyweight实现State和Strategy对象。

PROXY(代理)–对象结构型模式

  1. 意图 为其他对象提供一种代理以控制对这个对象的访问。
  2. 别名 Surrogate
  3. 动机 对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。
  4. 适用性
    在需要用比较通用和复杂的对象指针代替简单的指针的时候, 使用Proxy模式。下面是一些可以使用 Proxy 模式常见情况:

    1. 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用N XProxy类实现了这一目的。 Coplien[Cop92]称这种代理为“大使”(Ambassador)。
    2. 虚代理 (VirtualProxy)根据需要创建开销很大的对象。
    3. 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的 时候。
    4. 智能指引 ( SmartReference ) 取代了简单的指针, 它在访问对象时执行一些附加操作。 它的典型用途包括:
      • 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它
      • 当第一次引用一个持久对象时,将它装入内存。
      • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
  5. 结构

    这是运行时刻一种可能的代理结构的对象图。
  6. 参与者 略
  7. 协作
    代理根据其种类,在适当的时候向RealSubject转发请求。
  8. 效果
    Proxy模式在访问对象时引入了一定程度的间接性。 根据代理的类型, 附加的间接性有多种用途:

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

Proxy 模式还可以对用户隐藏另一种称之为 copy-on-write的优化方式, 该优化与根据需要创建对象有关。拷贝一个庞大而复杂的对象是一种开销很大的操作,如果这个拷贝根本没有被修改,那么这些开销就没有必要。用代理延迟这一拷贝过程,我们可以保证只有当这个对 象被修改的时候才对它进行拷贝。
在实现 Copy-on-write时必须对实体进行引用计数。 拷贝代理仅会增加引用计数。 只有当用户请求一个修改该实体的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减少实体的引用计数。当引用的数目为零时,这个实体将被删除。
Copy-on-Write可以大幅度的降低拷贝庞大实体时的开销。

  1. 实现 略
  2. 代码示例 略
  3. 已知应用 略
  4. 相关模式
    Adapter:适配器Adapter为它所适配的对象提供了一个不同的接口。 相反, 代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作, 因此,它的接口实际上可能只是实体接口的一个子集。
    Decorator:尽管decorator的实现部分与代理相似,但decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。
    代理的实现与decorator的实现类似,但是在相似的程度上有所差别。 ProtectionProxy的实现可能与dec orator的实现差不多。另一方面, RemoteProxy不包含对实体的直接引用, 而只是一个间接引用, 如 “主 机ID, 主机上的局部地址。”VirtualProxy开始的时候使用一个间接引用,例如一个文件名,但最终将获取并使用一个直接引用。

结构型模式的讨论

Adapter与Bridge

Adapter和 Bridge 模式通常被用于软件生命周期的不同阶段。 当你发现两个不兼容的类必须同时工作时,就有必要使用Adapter模式, 其目的一般是为了避免代码重复。此处耦合不可预见。相反, Bridge的使用者必 须事先知道: 一个抽象将有多个实现部分, 并且抽象和实现两者是独立演化的。 Adapter 模式在类已经设 计好后实施; 而Bridge模式在设计类之前实施。这并不意味着 Adapter模式不如 Bridge模式, 只是因为 它们针对了不同的问题 。
你可能认为facade是另外一组对象的适配器。 但这种解释忽视了一个事实:即Facade 定义一个新的接口,而 Adapter 则复用一个原有的接口。 记住, 适配器使两个已有的接口协同工作,而不是定义一个全新的接口。

Composite、Decorator与Proxy

Composite模式和 Decorator模式具有类似的结构图, 这说明它们都基于递归组合来组织可变数目的对象。
Decorator旨在使你能够不需要生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。 Composite 则有不同的目的, 它旨在构造类, 使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理。它重点不在于修饰, 而在于表示。
尽管它们的目的截然不同,但却具有互补性。因此Composite和Decorator模式通常协同使用。在使用这两种模式进行设计时,我们无需定义新的类,仅需将一些对象插接在一起即可构建应用。这时系统中将会有一个抽 象类, 它有一些composite子类和decorator子类, 还有一些实现系统的基本构建模块。此时, composit es和decorator将拥有共同的接口。从Decorator模式的角度看, composite 是一个ConcreteCompone nt。而从composite模式的角度看, decorator则是一个Leaf。 当然, 他们不一定要同时使用, 正如我们 所见, 它们的目的有很大的差别。
另一种与Decorator模式结构相似的模式是Proxy。 这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy 和 decorator对象的实现部分都保留了指向另一个对象的指针, 它们向这个对象发送请求.然而同样,它们具有不同的设计目的。
像Decorator模式一样, Proxy模式构成一个对象并为用户提供一致的接口。但与Decorator模式不同的是, Proxy模式不能动态地添加或分离性质, 它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。
在Proxy模式中,实体定义了关键功能,而 Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。 Decorator模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为Decorator模式中一个必不可少的部分。而在 Pr oxy模式中则不是这样,因为Proxy模式强调一种关系 (Proxy与它的实体之间的关系), 这种关系可以静态的 表达 。
模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。但这并不意味着这些模式不能结合使用。可以设想有一个 proxy-decorator, 它可以给 proxy 添加功能,或是一个decorator- proxy用来修饰一个远程对象。 尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。

行为模式

    行为模式涉及到算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。**这些模式刻划了在运行时难以跟踪的复杂的控制流**。它们将你的注意力从控制流转移到对象间的联系方式上来。
    行为类模式使用继承机制在类间分派行为。本章包括两个这样的模式。其中 TemplateMethod较为简单和常用。模板方法是一个算法的抽象定义,它逐步地定义该算法, 每一步调用一个抽象操作或一个原语操作,子类定义抽象操作以具体实现该算法。另一种行为类模式是Interpreter。 它将一个文法表示为一个类层次, 并 实现一个解释器作为这些类的实例上的一个操作。
    行为对象模式使用对象复合而不是继承。一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任一个对象都无法单独完成的任务。这里一个重要的问题是对等的对象如何互相了解对方。对等对象可以保持显式的对对方的引用,但那会增加它们的耦合度。在极端情况下,每一个对象都要了解所有其他的对象。 Mediat or在对等对象间引入一个 mediator对象以避免这种情况的出现。 mediator 提供了松耦合所需的间接性。
    Chain of Responsibility提供更松的耦合。它让你通过一条候选对象链隐式的向一个对象发送请求。根据运行时刻情况任一候选者都可以响应相应的请求。候选者的数目是任意的, 你可以在运行时刻决定哪些候选者参与到链中。
    Observer 模式定义并保持对象间的依赖关系。
    其他的行为对象模式常将行为封装在一个对象中并将请求指派给它。 Strategy 模式将算法封装在对象 中, 这样可以方便地指定和改变一个对象所使用的算法。 Command 模式 将请求封装在对象中,这样它就可作为参数来传递,也可以被存储在历史列表里,或者以其他方式使用。 State 模式封装一个对象的状态, 使得当 这个对象的状态对象变化时, 该对象可改变它的行为。 Visitor封装分布于多个类之间的行为,而 Iterato r则抽象了访问 和遍历一个集合中的对象的方式。

CHAIN OF RESPONSIBILITY(职责链)–对象行为模式

  1. 意图
    使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这 些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  2. 动机 略
  3. 适用性
    在以下条件下使用Responsibility 链:

    • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
    • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
    • 可处理一个请求的对象集合应被动态指定。
  4. 结构
  5. 参与者 略
  6. 协作 当客户提交一个请求时,请求沿链传递直至有一个 ConcreteHandler 对象负责处理它
  7. 效果
    Responsibility链有下列优点和缺点:

    1. 降低耦合度 该模式使得一个对象无需知道是其他哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。结果是,职责链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。
    2. 增强了给对象指派职责的灵活性 当在对象中分派职责时, 职责链给你更多的灵活性。你可以通过在运行时刻对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
    3. 不保证被接受 既然一个请求没有明确的接收者,那么就不能保证它一定会被处理 — 该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到 处理。
  8. 实现
  9. 代码示例 略
  10. 已知应用 略
  11. 职责链常与composite一起使用。这种情况下,一个构件的父构件可作为它的后续。

COMMAND(命令)–对象行为型模式

  1. 意图 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
  2. 别名 动作(Action),事务(Transaction)
  3. 动机 有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。
  4. 适用性
    当你有如下需求时,可使用Command模式:

    • 像上面讨论的 MenuItem对象那样, 抽象出待执行的动作以参数化某对象。 你可用过程语言中的回调(ca llback) 函数表达这种参数化机制。 所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。 Command模式是回调机制的一个面向对象的替代品
    • 在不同的时刻指定、排列和执行请求。一个 Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
    • 支持取消操作。 Command 的 Excute操作可在实施操作前将状态存储起来, 在取消操作时这个状态用来消除该操作的影响。 Command接口必须添加一个 Unexecute 操作,该操作取消上一次 Execute调 用的效果。 执行的命令被存储在一个历史列表中。 可通过向后和向前遍历这一列表并分别调用 Unexe cute和Execute来实现重数不限的”取消” 和 “重做”。
    • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在 Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用 Execute操作重新执行它们。
    • 用构建在原语操作上的高层操作构造一个系统 。 这样一种结构在支持事务 ( transaction) 的信息系统中很常见。一个事务封装了对数据的一组变动。 Command 模式提供了对事务进行建模的方法。 C ommand 有一个公共的接口, 使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
  5. 结构
  6. 参与者
    • Command — 声明执行操作的接口。
    • ConcreteCommand
      • 将一个接收者对象绑定于一个动作。
      • 调用接收者相应的操作,以实现 Execute 。
    • Client — 创建一个具体命令对象并设定它的接收者。
    • Invoker — 要求该命令执行这个请求。
    • Receiver — 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
  7. 协作
    • Client创建一个 ConcreteCommand对象并指定它的 Receiver对象。
    • 某Invoker对象存储该 ConcreteCommand对象。
    • 该Invoker通过调用 Command对象的 Execute 操作来提交一个请求。若该命令是可撤消的, Concr eteCommand就在执行Excute操作之前存储当前状态以用于取消该命令。
    • ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求。
  8. 效果
    Command模式有以下效果:

    1. Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
    2. Command是头等的对象。它们可像其他的对象一样被操纵和扩展。
    3. 你可将多个命令装配成一个复合命令。
    4. 增加新的 Command 很容易,因为这无需改变已有的类。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Composite模式可被用来实现宏命令。
    Memento模式可用来保持某个状态,命令用这一状态来取消它的效果。
    在被放入历史列表前必须被拷贝的命令起到一种原型的作用。

INTERPRETER(解释器)–类行为模式

  1. 意图
    给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  2. 动机
    如果一种特定类型的问题发生的频率足够高 , 那么可能就值得将该问题的各个实例表述为 一个简单语言中的句子。这样就可以构建一个解释器 , 该解释器通过解释这些句子来解决该问题。
    例如正则表达式。
  3. 适用性
    当有一个语言需要解释执行 , 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

    • 该文法简单对于复杂的文法 , 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式 , 这样可以节省空间而且还可能节省时间。
    • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的 , 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。
  4. 结构
  5. 参与者 略
  6. 协作
    • Client构建(或被给定)一个句子, 它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树. 然后初始化上下文并调用解释操作。
    • 每一非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。
    • 每一节点的解释操作用上下文来存储和访问解释器的状态。
  7. 效果
    解释器模式有下列的优点和不足:

    1. 易于改变和扩展文法 因为该模式使用类来表示文法规则 , 你可使用继承来改变或扩展该文法。已有的表达式可被增量式地改变 ,而新的表达式可定义为旧表达式的变体。
    2. 也易于实现文法 定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个编译器或语法分析程序生成器自动生成。
    3. 复杂的文法难以维护 解释器模式为文法中的每一条规则至少定义了一个类 ( 使用B N F定 义的文法规则需要更多的类 )。因此包含许多规则的文法可能难以管理和维护。可应用其他的设 计模式来缓解这一问题。但当文法非常复杂时, 其他的技术如语法分析程序或编译器生成器更为 合适。
    4. 增加了新的解释表达式的方式 解释器模式使得实现新表达式”计算”变得容易。 例如, 你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建新的解释表达式的方式 , 那么可以考虑使用 Visitor模式以避免修改这些代表文法的类。
  8. 实现 略 需要注意一点,解释器模式并未解释如何创建一个抽象的语法树。
  9. 代码示例 略
  10. 已知应用 略
  11. 相关模式
    Composite模式:抽象语法树是一个复合模式的实例。
    Flyweight模式:说明了如何在抽象语法树中共享终结符。
    Iterator:解释器可用一个迭代器遍历该结构。
    Visitor:可用来在一个类中维护抽象语法树中的各节点的行为。

ITERATOR(迭代器)–对象行为型模式

  1. 意图
    提供一种方法顺序访问一个聚合对象中各个元素 , 而又不需暴露该对象的内部表示。
  2. 别名
    游标(Cursor)。
  3. 动机
    一个聚合对象, 如列表(list), 应该提供一种方法来让别人可以访问它的元素,而又不需暴
    露它的内部结构.
    这一模式的关键思想是将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器 (iterator)对象中。 迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素; 即, 它知道哪些元素已经遍历过了。
  4. 适用性
    迭代器模式可用来:

    • 访问一个聚合对象的内容而无需暴露它的内部表示。
    • 支持对聚合对象的多种遍历。
    • 为遍历不同的聚合结构提供一个统一的接口 (即, 支持多态迭代)。
  5. 结构
  6. 参与者 略
  7. 协作 ConcreteIterator跟踪聚合中的当前对象,并能够计算出待遍历的后继对象。
  8. 效果
    迭代器模式有三个重要的作用:

    1. 它支持以不同的方式遍历一个聚合 复杂的聚合可用多种方式进行遍历。例如 , 代码生成和语义检查要遍历语法分析树。代码生成可以按中序或者按前序来遍历语法分析树。迭代器模式使得改变遍历算法变得很容易: 仅需用一个不同的迭代器的实例代替原先的实例即可。 你也可以自己定义迭代器的子类以支持新的遍历。
    2. 迭代器简化了聚合的接口 有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了。这样就简化了聚合的接口。
    3. 在同一个聚合上可以有多个遍历 每个迭代器保持它自己的遍历状态。因此你可以同时进行多个遍历。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Composite: 迭代器经常被应用到象复合这样的递归结构上。
    Factory Method: 多态迭代器靠Factory Method来例化适当的迭代器子类。
    Memento:常与迭代器模式一起使用。迭代器可使用一个memento来捕获一个迭代的状态。迭代器在其内部存储memento。

MEDIATOR(中介者)–对象行为型模式

  1. 意图
    用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。这个好理解,估计租过房的人都有感觉。。
  2. 动机 略
  3. 适用性
    在下列情况下使用中介者模式 :

    • 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
    • 一个对象引用其他很多对象并且直接与这些对象通信 ,导致难以复用该对象。
    • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
  4. 结构
  5. 参与者
    • Mediator(中介者)
      • 中介者定义一个接口用于与各同事(Colleague)对象通信。
    • ConcreteMediator (具体中介者)
      • 具体中介者通过协调各同事对象实现协作行为。
      • 了解并维护它的各个同事。
    • Colleague class(同事类)
      • 每一个同事类都知道它的中介者对象。
      • 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。

  1. 协作
    同事向一个中介者对象发送和接收请求。中介者在各同事间适当地转发请求以实现协作行为。
  2. 效果
    中介者模式有以下优点和缺点:

    1. 减少了子类生成 Mediator将原本分布于多个对象间的行为集中在一起。 改变这些行为只需生成Med itator的子类即可。这样各个 Colleague 类可被重用。
    2. 它将各Colleague解耦 Mediator有利于各Colleague间的松耦合. 你可以独立的改变和复用各C olleague类和 Mediator 类。
    3. 它简化了对象协议 用Mediator和各Colleague间的一对多的交互来代替多对多的交互。一对多的关系更易于理解、维护和扩展。
    4. 它对对象如何协作进行了抽象 将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统 中的对象是如何交互的。
    5. 它使控制集中化 中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议 , 它可能变得比任一个 Colleague都复杂。 这可能使得中介者自身成为一个难于维护的庞然大物。

MEMENTO(备忘录)– 对象行为型模式

  1. 意图
    在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 这样以后就可将该对象恢复到原先保存的状态。
  2. 别名
    Token
  3. 动机 有时有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复
    过来,需要实现检查点和取消机制 , 而要实现这些机制,你必须事先将状态信息保存在某处, 这样才能将对象恢复到它们先前的状态。但是对象通常封装了其部分或所有的状态信息 , 使得 其状态不能被其他对象访问,也就不可能在该对象之外保存其状态。而暴露其内部状态又将 违反封装的原则,可能有损应用的可靠性和可扩展性。
  4. 适用性
    在以下情况下使用备忘录模式:

    • 必须保存一个对象在某一个时刻的 (部分)状态, 这样以后需要时它才能恢复到先前的状态。
    • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
  5. 结构
  6. 参与者 略
  7. 协作
    • 管理器向原发器请求一个备忘录 , 保留一段时间后 ,将其送回给原发器 , 如下面的交互图所示。
      有时管理者不会将备忘录返回给原发器 , 因为原发器可能根本不需要退到先前的状态。
    • 备忘录是被动的。只有创建备忘录的原发器会对它的状态进行赋值和检索。
  8. 效果
    备忘录模式有以下一些效果 :

    1. 保持封装边界 使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。该模式把可能很复杂的 Originator 内部信息对其他对象屏蔽起来 , 从而保持了封装边界。
    2. 它简化了原发器 在其他的保持封装性的设计中, Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的重任交给了Originator。 让客户管理它们请求的状态将会简化Originator, 并 且使得客户工作结束时无需通知原发器。
    3. 使用备忘录可能代价很高 如果原发器在生成备忘录时必须拷贝并存储大量的信息, 或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销。除非封装和恢复Originator状态的开销不大,否则该模式可能并不合适。
    4. 定义窄接口和宽接口 在一些语言中可能难以保证只有原发器可访问备忘录的状态。
    5. 维护备忘录的潜在代价 管理器负责删除它所维护的备忘录。然而 , 管理器不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理器,可能会产生大量的存储 开销。
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Command: 命令可使用备忘录来为可撤消的操作维护状态。
    Iterator: 如前所述备忘录可用于迭代 .

OBSERVER(观察者) — 对象行为型模式

  1. 意图
    定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时 , 所有依赖于它的对象 都得到通知并被自动更新。
  2. 别名
    依赖 , 发布 -订阅
  3. 动机 将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一
    致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。
  4. 适用性
    在以下任一情况下可以使用观察者模式 :

    • 当一个抽象模型有两个方面 , 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
    • 当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。
    • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。
  5. 结构
  6. 参与者 略
  7. 协作
    • 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
    • 在得到一个具体目标的改变通知后 , ConcreteObserver 对象可向目标对象查询信息。ConcreteSubject使用这些信息以使它的状态与目标对象的状态一致。 下面的交互图说明了一个目标对象和两个观察者之间的协作 :

      注意发出改变请求的 Observer对象并不立即更新 , 而是将其推迟到它从目标得到一个通知之后。 Notify不总是由目标对象调用。 它也可被一个观察者或其它对象调用。
  8. 效果
    Observer模式允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用 其观察者, 反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
    下面是观察者模式其它一些优缺点 :

    1. 目标和观察者间的抽象耦合
    2. 支持广播通信
    3. 意外的更新
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Mediator : 通过封装复杂的更新语义 , ChangeManager充当目标和观察者之间的中介者。
    Singleton : ChangeManager 可使用Singleton模式来保证它是唯一的并且是可全局访问的。

STATE(状态) — 对象行为型模式

  1. 意图 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
  2. 别名
    状态对象(Objects for States)
  3. 动机 略
  4. 适用性
    在下面的两种情况下均可使用 State 模式 :

    • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
    • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State 模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
  5. 结构
  6. 参与者 略
  7. 协作
    • Context将与状态相关的请求委托给当前的 ConcreteState 对象处理。
    • Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context。
    • Context是客户使用的主要接口。客户可用状态对象来配置一个Context ,一旦一个Context配置完毕 , 它的客户不再需要直接与状态对象打交道。
    • Context 或ConcreteState子类都可决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行状态转换。
  8. 效果
    State 模式有下面一些效果 :

    1. 它将与特定状态相关的行为局部化, 并且将不同状态的行为分割开来
    2. 它使得状态转换显式化
    3. State对象可被共享
  9. 实现 略
  10. 代码示例
  11. 已知应用
  12. 相关模式
    Flyweight模式解释了何时以及怎样共享状态对象。
    状态对象通常是Singleton。

STRATEGY(策略) — 对象行为型模式

  1. 意图
    定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独 立于使用它的客户而变化。
  2. 别名
    政策(Policy)
  3. 动机 略
  4. 适用性
    当存在以下情况时使用 Strategy 模式

    • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
    • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 [ H O 8 7 ] ,可以使用策略模式。
    • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
    • 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的 Strategy 类中以代替这些条件语句。(和状态模式有点像,也是替代条件语句)
  5. 结构
  6. 参与者 略
  7. 协作
    • Strategy和 Context 相互作用以实现选定的算法。当算法被调用时, Context 可以将该算法 所需要的所有数据都传递给该Strategy 。或者, Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
    • Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConStrategy对象给该Context;这样 , 客户仅与Context交互。通常有一系列的ConStrategy 类可供 客户从中选择。
  8. 效果
    Strategy模式有下面的一些优点和缺点 :

    1. 相关算法系列
    2. 一个替代继承的方法
    3. 消除了一些条件语句 含有许多条件语句的代码通常意味着需要使用Strategy模式。
    4. 实现的选择
    5. 客户必须了解不同的Strategy 本模式有一个潜在的缺点, 就是一个客户要选择一个合适的Strategy 就必须知道这些Strategy到底有何不同
    6. Strategy和Context之间的通信开销
    7. 增加了对象的数目
  9. 实现 略
  10. 代码示例 略
  11. 已知应用 略
  12. 相关模式
    Flyweight : Strategy对象经常是很好的轻量级对象。

TEMPLATE METHOD(模板方法) — 类行为型模式

  1. 意图
    定义一个操作中的算法的骨架, 而将一些步骤延迟到子类中。 TEMPLATE METHOD使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  2. 动机 略
  3. 适应性
    模板方法应用于下列情况:

    • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
    • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是O p d y k e和 J o h n s o n所描述过的“重分解以一般化”的一个很好的例子 [ O J 9 3 ] 。首先识别 现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的 操作的模板方法来替换这些不同的代码。
    • 控制子类扩展。 模板方法只在特定点调用”hook“操作,这样就只允许在这些点进行扩展。
  4. 结构
  5. 参与者 略
  6. 协作 ConcreteClass 靠 AbstractClass 来实现算法中不变的步骤。
  7. 效果
    模板方法导致一种反向的控制结构,这种结构有时被称为“好莱坞法则”,即“别找我们,我们找你” [ S w e 8 5 ] 。 这指的是一个父类调用一个子类的操作, 而不是相反。 模板方法调用下列类型的操作:

    • 具体的操作( ConcreteClass 或 对客户类的操作 )。
    • 具体的 AbstractClass 的操作 (即, 通常对子类有用的操作)。
    • 原语操作(即,抽象操作)。
    • FactoryMethod 。
    • 钩子操作(hook operations),它提供了缺省的行为,子类可以在必要时进行扩展。一个钩子操作在缺省操作通常是一个空操作。
  8. 实现 略
  9. 代码示例
  10. 已知应用 略
  11. 相关模式
    FactoryMethod模式常被模板方法调用。
    Strategy : 模板方法使用继承来改变算法的一部分。 Strategy使用委托来改变整个算法。

VISITOR(访问者)–对象行为模式

  1. 意图
    表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提 下定义作用于这些元素的新操作。
  2. 动机 略
  3. 适用性
    在下列情况下使用Visitor模式:

    • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
    • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。 Visitor使得你可以将相关的操作集中起来定义在一个类中。当 该 对 象 结 构 被 很 多 应 用 共 享 时 , 用 Vi s i t o r 模 式 让 每 个 应 用 仅 包 含 需 要 用 到 的 操 作 。
    • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
  4. 结构
  5. 参与者
    • Visitor(访问者,如 NodeVisitor)
      • 为该对象结构中 ConcreteElement的每一个类声明一个 Visit 操作。 该操作的名字和特征标识了发送Visit请求给该访问者的那个类。 这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。
    • ConcreteVisitor(具体访问者,如 TypeCheckingVisitor)
      • 实现每个由Visitor声明的操作。 每个操作实现本算法的一部分 , 而该算法片断乃是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果。
    • Element(元素,如 N o d e )
      • 定义一个 Accept 操作,它以一个访问者为参数。
    • ConcreteElement( 具体元素)
      • 实现Accept操作,该操作以一个访问者为参数。
    • ObjectStructure(对象结构)
      • 能枚举它的元素。
      • 可以提供一个高层的接口以允许该访问者访问它的元素。
      • 可以是一个复合( 参见Composite) 或 是一个集合, 如一个列表或一个无序集合。
  6. 协作
    • 一个使用Visitor 模式的客户必须创建一个 ConcreteVisitor 对象 , 然后遍历该对象结构,并用该访问者访问每一个元素。
    • 当一个元素被访问时,它调用对应于它的类的 Visitor 操 作 。 如 果 必 要 , 该 元 素 将 自 身 作为这个操作的一个参数以便该访问者访问它的状态。 下面的交互框图说明了一个对象结构、一个访问者和两个元素之间的协作。
  7. 效果
    下面是访问者模式的一些优缺点:

    1. 访问者模式使得易于增加新的操作 访问者使得增加依赖于复杂对象结构的构件的操
      作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反, 如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。
    2. 访问者集中相关的操作而分离无关的操作
    3. 增加新的 ConcreteElement类很困难
    4. 通过类层次进行访问
    5. 累积状态
    6. 破坏封装
  8. 实现 略
  9. 代码示例 略
  10. 已知应用 略
  11. 相关模式
    Composite : 访问者可以用于对一个由Composite模式定义的对象结构进行操作。
    Interpreter: 访问者可以用于解释 。

行为模式的讨论

  1. 封装变化
  2. 对象作为参数
  3. 通信应该被封装还是被分布
  4. 对发送者和接收者解耦

未经允许不得转载:演道网 » 设计模式:可复用面向对象软件的基础

赞 (3)
分享到:更多 ()