设计模式与原则

本文是对设计模式的总结, 大量参考了 深入设计模式 一书, 原书非常值得一读!

面向对象程序设计基础

面向对象设计的四个基本概念使其区别于其他程序设计范式

  • 多态
  • 抽象
  • 封装
  • 继承

抽象

抽象是一种反映真实世界对象或现象中特定内容的模型, 它能高精度地反映所有与特定内容相关的详细信息, 同时忽略其他内容.

多态

多态是指程序能够检测对象所属的实际类, 并在当前上下文不知道其真实类型的情况下调用其实现的能力.

封装

封装是指一个对象对其他对象隐藏其部分状态和行为, 而仅向程序其他部分暴露有限的接口的能力.

封装某个内容意味着使用关键字 private 私有来对其进行修饰, 这样仅有其所在类中的方法才能访问这些内容. 还有一种限制程度较小的关键字 protected 保护, 其所修饰的对象仅允许父类访问其类中的成员.

继承

继承是指在根据已有类创建新类的能力. 继承最主要的好处是代码复用.

使用继承后, 子类将拥有与其父类相同的接口. 如果父类中声明了某个方法, 那么你将无法在子类中隐藏该方法. 你还必须实现所有的抽象方法, 即使它们对于你的子类而言没有意义.

面向接口进行开发, 而不是面向实现

面向接口进行开发, 而不是面向实现; 依赖于抽象类型, 而不是具体类.

当你需要两个类进行合作时, 可以让其中一个类依赖于另一个类. 但是, 你可用另外一种更灵活的方式来设置对象之间的合作关系.

  1. 确定一个对象对另一对象的确切需求: 它需执行哪些方法?
  2. 在一个新的接口或抽象类中描述这些方法.
  3. 让被依赖的类实现该接口.
  4. 现在让有需求的类依赖于这个接口, 而不依赖于具体的类. 你仍可与原始类中的对象进行互动, 但现在其连接将会灵活 得多.

组合优于继承

相对于组合, 继承有如下的缺点

  • 子类不能减少超类的接口. 你必须实现父类中所有的抽象方 法, 即使它们没什么用.
  • 在重写方法时, 你需要确保新行为与其基类中的版本兼容. 这一点很重要, 因为子类的所有对象都可能被传递给以超类 对象为参数的任何代码, 相信你不会希望这些代码崩溃的.
  • 继承打破了超类的封装, 因为子类拥有访问父类内部详细内容的权限. 此外还可能会有相反的情况出现, 那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容.
  • 子类与超类紧密耦合. 超类中的任何修改都可能会破坏子类的功能.
  • 通过继承复用代码可能导致平行继承体系的产生. 继承通常仅发生在一个维度中. 只要出现了两个以上的维度, 你就必须创建数量巨大的类组合, 从而使类层次结构膨胀到不可思议的程度.

组合是代替继承的一种方法. 继承代表类之间的 关系 (汽车是交通工具), 而组合则代表 关系 (汽车有一个引擎).

必须一提的是, 这个原则也能应用于聚合 (一种更松弛的组 合变体, 一个对象可引用另一个对象, 但并不管理其生命周 期). 例如: 一辆汽车上有司机, 但是司机也可能会使用另一辆汽车, 或者选择步行而不使用汽车.

SOLID 原则

与生活中所有事情一样, 盲目遵守这些原则可能会弊大于利. 在程序架构中应用这些原则可能会使其变得过于复杂. 有原则是件好事, 但是也要时刻从实用的角度来考量, 不要把这里的每句话当作放之四海皆准的教条.

Single Responsibility Principle(单一职责原则)

尽量让每个类只负责软件中的一个功能, 并将该功能完全封装 (你也可称之为隐藏) 在该类中.

Open/Closed Principle (开放 - 封闭原则)

对修改关闭, 对扩展开放

这条原则并不能应用于所有对类进行的修改中. 如果你发现类中存在缺陷, 直接对其进行修复即可, 不要为它创建子类. 子类不应该对其父类的问题负责.

Liskov Substitution Principle(里氏替换原则)

父类可以被子类无缝替换, 且原有功能不受任何影响

Interface Segregation Principle(接口隔离原则)

使用多个专门的协议, 而不是一个庞大臃肿的协议

与其他原则一样, 你可能会过度使用这条原则. 不要进一步 划分已经非常具体的接口. 记住, 创建的接口越多, 代码就越复杂. 因此要保持平衡.

Dependency Inversion Principle(依赖倒转原则)

高层次的类不应该依赖于低层次的类. 两者都应该依赖于抽象接口. 抽象接口不应依赖于具体实现. 具体实现应该依赖于抽象接口.

  • 低层次的类: 实现基础操作 (例如磁盘操作, 传输网络数据和连接数据库等).
  • 高层次类: 包含复杂业务逻辑以指导低层次类执行特定操作.

迪米特法则

一个对象应当对其他对象有尽可能少的了解.

遵守了迪米特法则, 也就意味着达到了 高内聚, 低耦合

创建型模式

创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性.

Factory Method(工厂方法模式) / Virtual Constructor(虚拟构造函数)

工厂方法模式在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型.

结构

优缺点

  • 优点
    • 你可以避免创建者和具体产品之间的紧密耦合.
    • 单一职责原则. 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护.
    • 开闭原则. 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型.
  • 缺点
    • 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂. 最好的情况是将该模式引入创建者类的现有层次结构中.

Abstract Factory(抽象工厂模式)

抽象工厂模式能创建一系列相关的对象, 而无需指定其具体类.

结构

优缺点

  • 优点
    • 你可以确保同一工厂生成的产品相互匹配.
    • 你可以避免客户端和具体产品代码的耦合.
    • 单一职责原则. 你可以将产品生成代码抽取到同一位置, 使得代码易于维护.
    • 开闭原则. 向应用程序中引入新产品变体时, 你无需修改客户端代码.
  • 缺点
    • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂.

Builder(生成器模式) / 建造者模式

生成器是一种创建型设计模式, 使你能够分步骤创建复杂对象. 该模式允许你使用相同的创建代码生成不同类型和形式的对象.

结构

优缺点

  • 优点
    • 生成器是一种创建型设计模式, 使你能够分步骤创建复杂对象. 该模式允许你使用相同的创建代码生成不同类型和形式的对象.
    • 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤.
    • 生成不同形式的产品时, 你可以复用相同的制造代码.
    • 单一职责原则. 你可以将复杂构造代码从产品的业务逻辑中分离出来.
  • 缺点
    • 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加.

Prototype(原型模式) / Clone(克隆模式)

原型是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类.

结构

优缺点

  • 优点
    • 原型是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类.
    • 你可以克隆对象, 而无需与它们所属的具体类相耦合.
    • 你可以克隆预生成原型, 避免反复运行初始化代码.
    • 你可以更方便地生成复杂对象.
    • 你可以用继承以外的方式来处理复杂对象的不同配置.
  • 缺点
    • 克隆包含循环引用的复杂对象可能会非常麻烦.

Singleton(单例模式)

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点.

结构

优缺点

  • 优点
    • 你可以保证一个类只有一个实例.
    • 你获得了一个指向该实例的全局访问节点.
    • 仅在首次请求单例对象时对其进行初始化.
  • 缺点
    • 违反了单一职责原则. 该模式同时解决了两个问题.
    • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等.
    • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象.
    • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象. 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法. 要么干脆不编写测试代码, 或者不使用单例模式.

总结

  • 在许多设计工作的初期都会使用工厂方法 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂, 原型或生成器 (更灵活但更加复杂).
  • 工厂方法是模板方法的一种特殊形式. 同时, 工厂方法可以作为一个大型模板方法中的一个步骤.
  • 生成器重点关注如何分步生成复杂对象. 抽象工厂专门用于生产一系列相关对象. 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤.
  • 你可以在创建复杂组合树时使用生成器, 因为这可使其构造步骤以递归的方式运行.
  • 你可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作.
  • 抽象工厂, 生成器和原型都可以用单例来实现.
  • 原型可用于保存命令的历史记录.
  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法.
  • 大量使用组合和装饰的设计通常可从对于原型的使用中获益. 你可以通过该模式来复制复杂结构, 而非从零开始重新构造.
  • 原型并不基于继承, 因此没有继承的缺点. 另一方面, 原型需要对被复制对象进行复杂的初始化. 工厂方法基于继承, 但是它不需要初始化步骤.
  • 有时候原型可以作为备忘录的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建.
  • 外观类通常可以转换为单例类, 因为在大部分情况下一个外观对象就足够了.
  • 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观.
  • 你可以将抽象工厂和桥接搭配使用. 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用. 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性.
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例类似了. 但这两个模式有两个根本性的不同.
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同.
    2. 单例对象可以是可变的. 享元对象是不可变的.

结构性模式

结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效.

Adapter(适配器模式) / 封装器模式

适配器是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作.

  • 优点
    • 单一职责原则, 你可以将接口或数据转换代码从程序主要业务逻辑中分离.
    • 开闭原则. 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器.
  • 缺点
  • 代码整体复杂度增加, 因为你需要新增一系列接口和类. 有时直接更改服务类使其与其他代码兼容会更简单.

Bridge(桥接模式)

桥接是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用.

  • 优点
    • 你可以创建与平台无关的类和程序.
    • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息.
    • 开闭原则. 你可以新增抽象部分和实现部分, 且它们之间不会相互影响.
    • 单一职责原则. 抽象部分专注于处理高层逻辑, 实现部分处理平台细节.
  • 缺点
    • 对高内聚的类使用该模式可能会让代码更加复杂.

Composite(组合模式) / Object Tree(对象树模式)

组合是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们.

  • 优点:
    • 你可以利用多态和递归机制更方便地使用复杂树结构.
    • 开闭原则. 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分.
  • 缺点:
    • 对于功能差异较大的类, 提供公共接口或许会有困难. 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解.

Decorator(装饰模式) / Wrapper(装饰器模式)

  • 优点
    • 装饰是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为.
    • 你无需创建新子类即可扩展对象的行为.
    • 你可以在运行时添加或删除对象的功能.
    • 你可以用多个装饰封装对象来组合几种行为.
    • 单一职责原则. 你可以将实现了许多不同行为的一个大类拆分为多个较小的类.
  • 缺点
    • 在封装器栈中删除特定封装器比较困难.
    • 实现行为不受装饰栈顺序影响的装饰比较困难.
    • 各层的初始化配置代码看上去可能会很糟糕.

Facade(外观模式) / 门面模式

外观是一种结构型设计模式, 能为程序库, 框架或其他复杂类提供一个简单的接口.

  • 优点
    • 你可以让自己的代码独立于复杂子系统.
  • 缺点
    • 外观可能成为与程序中所有类都耦合的上帝对象.

Flyweight(享元模式) / Cache(缓存模式)

享元是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象.

  • 优点
    • 如果程序中有很多相似对象, 那么你将可以节省大量内存.
    • 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据.
  • 缺点
    • 代码会变得更加复杂. 团队中的新成员总是会问: 为什么要像这样拆分一个实体的状态?

Proxy(代理模式)

代理是一种结构型设计模式, 让你能够提供对象的替代品或其占位符. 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理.

  • 优点
    • 你可以在客户端毫无察觉的情况下控制服务对象.
    • 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理.
    • 即使服务对象还未准备好或不存在, 代理也可以正常工作.
    • 开闭原则. 你可以在不对服务或客户端做出修改的情况下创建新代理.
  • 缺点
    • 代码可能会变得复杂, 因为需要新建许多类.
    • 服务响应可能会延迟.

总结

  • 桥接通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发. 另一方面, 适配器通常在已有程序中使用, 让相互不兼容的类能很好地合作.
  • 你可以将抽象工厂和桥接搭配使用. 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用. 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性.
  • 你可以在创建复杂组合树时使用生成器, 因为这可使其构造步骤以递归的方式运行.
  • 你可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作.
  • 适配器可以对已有对象的接口进行修改, 装饰则能在不改变对象接口的前提下强化对象功能. 此外, 装饰还支持递归组合, 适配器则无法实现.
  • 适配器能为被封装对象提供不同的接口, 代理能为对象提供相同的接口, 装饰则能为对象提供加强的接口.
  • 外观为现有对象定义了一个新接口, 适配器则会试图运用已有的接口. 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上.
  • 桥接, 状态和策略 (在某种程度上包括适配器) 模式的接口非常相似. 实际上, 它们都基于组合模式 —— 即将工作委派给其他对象, 不过也各自解决了不同的问题. 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题.
  • 责任链通常和组合模式结合使用. 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部.
  • 责任链和装饰模式的类结构非常相似. 两者都依赖递归组合将需要执行的操作传递给一系列对象. 但是, 两者有几点重要的不同之处. 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求. 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为. 此外, 装饰无法中断请求的传递.
  • 你可以使用迭代器来遍历组合树.
  • 你可以使用访问者对整个组合树执行操作.
  • 你可以使用享元实现组合树的共享叶节点以节省内存.
  • 大量使用组合和装饰的设计通常可从对于原型的使用中获益. 你可以通过该模式来复制复杂结构, 而非从零开始重新构造.
  • 装饰可让你更改对象的外表, 策略则让你能够改变其本质.
  • 装饰和代理有着相似的结构, 但是其意图却非常不同. 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象. 两者之间的不同之处 在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制.
  • 你可以使用享元实现组合树的共享叶节点以节省内存.
  • 享元展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统.
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例类似了. 但这两个模式有两个根本性的不同.
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同.
    2. 单例对象可以是可变的. 享元对象是不可变的.
  • 外观与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化. 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同.
  • 装饰和代理有着相似的结构, 但是其意图却非常不同. 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象. 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制.

行为模式

行为模式负责对象间的高效沟通和职责委派.

Chain of Responsibility(职责链模式) / Chain of Command(命令链)

责任链是一种行为设计模式, 允许你将请求沿着处理者链进行发送. 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者.

  • 优点
    • 你可以控制请求处理的顺序.
    • 单一职责原则. 你可对发起操作和执行操作的类进行解耦.
    • 开闭原则. 你可以在不更改现有代码的情况下在程序中新增处理者.
  • 缺点
    • 部分请求可能未被处理.

Command(命令模式) / Action(动作) / Transaction(事务)

命令是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象. 该转换让你能根据不同的请求将方法参数化, 延迟请求执行或将其放入队列中, 且能实现可撤销操作

  • 优点
    • 单一职责原则. 你可以解耦触发和执行操作的类.
    • 开闭原则. 你可以在不修改已有客户端代码的情况下在程序中创建新的命令.
    • 你可以实现撤销和恢复功能.
    • 你可以实现操作的延迟执行.
    • 你可以将一组简单命令组合成一个复杂命令.
  • 缺点
    • 代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了一个全新的层次.

Iterator(迭代器模式)

迭代器是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表, 栈和树等) 的情况下遍历集合中所有的元素.

  • 优点
    • 单一职责原则. 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理.
    • 开闭原则. 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码.
    • 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态.
    • 相似的, 你可以暂停遍历并在需要时继续.
  • 缺点
    • 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正.
    • 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低.

Mediator(中介者模式) / Intermeiary(调解人) / Controller(控制器)

中介者是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系. 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作.

  • 优点:
    • 单一职责原则. 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护.
    • 开闭原则. 你无需修改实际组件就能增加新的中介者.
    • 你可以减轻应用中多个组件间的耦合情况.
    • 你可以更方便地复用各个组件.
  • 缺点
    • 一段时间后, 中介者可能会演化成为上帝对象.

Memento(备忘录模式) / Snapshot(快照)

备忘录是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态.

  • 优点
    • 你可以在不破坏对象封装情况的前提下创建对象状态快照.
    • 你可以通过让负责人维护原发器状态历史记录来简化原发器代码.
  • 缺点
    • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存.
    • 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录.
    • 绝大部分动态编程语言 (例如 PHP, Python 和 JavaScript) 不能确保备忘录中的状态不被修改.

Observer(观察者模式) / Event-Subscriber(事件订阅者) / Listener(监听者)

观察者是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 观察 该对象的其他对象

  • 优点
    • 开闭原则. 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类).
    • 你可以在运行时建立对象之间的联系.
  • 缺点
    • 订阅者的通知顺序是随机的.

State(状态模式)

状态是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样.

  • 优点
    • 单一职责原则. 将与特定状态相关的代码放在单独的类中.
    • 开闭原则. 无需修改已有状态类和上下文就能引入新状态.
    • 通过消除臃肿的状态机条件语句简化上下文代码.
  • 缺点
    • 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作.

Strategy(策略模式)

策略是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换.

  • 优点
    • 你可以在运行时切换对象内的算法.
    • 你可以将算法的实现和使用算法的代码隔离开来.
    • 你可以使用组合来代替继承.
    • 开闭原则. 你无需对上下文进行修改就能够引入新的策略.
  • 缺点
    • 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口. 使用该模式只会让程序过于复杂.
    • 客户端必须知晓策略间的不同 —— 它需要选择合适的策略.
    • 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法. 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁.

Template Method(模板方法模式)

模板方法是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤.

  • 优点
    • 你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小.
    • 你可将重复代码提取到一个超类中.
  • 缺点
    • 部分客户端可能会受到算法框架的限制.
    • 通过子类抑制默认步骤实现可能会导致违反里氏替换原则.
    • 模板方法中的步骤越多, 其维护工作就可能会越困难.

Visitor(访问者模式)

访问者是一种行为设计模式, 它能将算法与其所作用的对象隔离开来.

  • 开闭原则. 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改.
  • 单一职责原则. 可将同一行为的不同版本移到同一个类中.
  • 访问者对象可以在与各种对象交互时收集一些有用的信息. 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助.
  • 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者.
  • 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限.

总结

  • 责任链, 命令, 中介者和观察者用于处理请求发送者和接收者之间的不同连接方式
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理.
    • 命令在发送者和请求者之间建立单向连接.
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通.
    • 观察者允许接收者动态地订阅或取消接收请求.
  • 责任链通常和组合模式结合使用. 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部
  • 责任链的管理者可使用命令模式实现. 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作. 还有另外一种实现方式, 那就是请求自身就是一个命令对象. 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作.
  • 你可以同时使用命令和备忘录来实现”撤销”. 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态.
  • 你可以同时使用工厂方法和迭代器来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配.
  • 你可以同时使用备忘录和迭代器来获取当前迭代器的状态, 并且在需要的时候进行回滚.
  • 可以同时使用访问者和迭代器来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同.
  • 原型可用于保存命令的历史记录.
  • 你可以将访问者视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作.
  • 命令和策略看上去很像, 因为两者都能通过某些行为来参数化对象. 但是, 它们的意图有非常大的不同
    • 你可以使用命令来将任何操作转换为对象. 操作的参数将成为对象的成员变量. 你可以通过转换来延迟操作的执行, 将操作放入队列, 保存历史命令或者向远程服务发送命令等
    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法.
  • 外观和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作.
    • 中介者将系统中组件的沟通行为中心化. 各组件只知道中介者对象, 无法直接相互交流.
    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能. 子系统本身不会意识到外观的存在. 子系统中的对象可以直接进行交流.
  • 状态可被视为策略的扩展. 两者都基于组合机制: 它们都通过将部分工作委派给”帮手”对象来改变其在不同情景下的行为. 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在. 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态.
  • 装饰可让你更改对象的外表, 策略则让你能够改变其本质.
  • 模板方法基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法. 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为. 模板方法在类层次上运作, 因此它是静态的. 策略在对象层次上运作, 因此允许在运行时切换行为.
  • 工厂方法是模板方法的一种特殊形式. 同时, 工厂方法可以作为一个大型模板方法中的一个步骤.

Ref

本博客文章采用 CC 4.0 协议,转载需注明出处和作者。

鼓励作者