基于 Design Patterns: Elements of Reusable Object-Oriented Software 编写

介绍

设计模式有四大必备元素:

  • 模式名
  • 问题:何时应用此模式
  • 解决方案:应用于不同情况下的模板
  • 后果:应用该模式的结果与取舍

多种设计模式可以组合使用,事实上也经常组合使用

设计模式解决问题的方法:

  • 找到合适的对象
  • 确定对象的粒度
  • 具体说明对象接口
  • 具体说明对象实现
  • 类与接口继承
  • 给接口编程,而不是向实现

为了让复用机制生效

  • 继承与组合——倾向于组合
  • 指派
  • 继承与参数化类型

运行时和编译时结构

  • aggregation:一个对象拥有另一个对象,即两者有相同的生命周期
  • acquaintance:一个对象仅仅知道另一个对象

为改变而设计:

  • 通过

应用程序:

  • 工具集
  • 框架

创建模式

抽象工厂 Abstract Factory

目的:在具体说明具体类的情况下创建相关的对象家族

应用

  • 一个应该被配置为多种产品家族之一的系统
  • 一个家族的相关产品被一起使用
  • 提供一类库的产品

结构

抽象工厂

参与者

  • 抽象工厂 - 具体工厂
  • 抽象产品 - 具体产品
  • 客户

协作方式

  • 运行时具体的工厂类被创建
  • 抽象工厂将产品的创建延迟到具体工厂子类中

后果

  • 隔离了具体的类
  • 让改变产品家族更容易
  • 提高了产品之间的一致度
  • 但是支持新产品种类较难

实现

  • 工厂作为单例
  • 创建产品
  • 定义扩展工厂

相关模式:

  • 抽象工厂类通过用工厂方法实现,也可以使用原型
  • 具体工厂是通常是一个单例

建造者 Builder

意图:将复杂对象的构造从表示中分离,使得相同的构造过程可以创建不同的表示

应用

  • 创建复杂对象的方法独立于构成对象的部分
  • 创建过程必须允许不同的构造对象的表示

结构

建造者

参与者

  • 建造者 - 具体建造者
  • 指导者:使用建造者的接口建造对象
  • 产品

协作

  • 客户创建指导者对象并用期望的建造者对象配置
  • 当有产品的某部分应该构建时,指导者提示建造者
  • 建造者处理请求并添加部分
  • 客户从建造者获取产品

后果

  • 产品的内部表示可变
  • 隔离构造和表示
  • 对构造过程更好的控制

实现

  • 装配和构造接口
  • 建造者默认空方法

相关模式

  • 抽象工厂非常类似,但侧重于创建一个家族的产品对象,每一步都会返回产品
  • 建造者一步一步地创建一个复杂的产品,并在最后返回一整个产品

工厂方法 Factory Method

意图:定义创建对象的接口,但是让子类决定实例化哪个类,即将实例化延迟到子类

应用

  • 一个类不参与它即将创建的对象类
  • 一个需要它的子类类具体说明创建的对象

工厂方法

协作:创造者依赖于子类定义工厂方法,返回一个合适的具体产品的实例

后果

  • 给子类提供了 hook
  • 连接平行的类层次结构

实现

  • 两个主要变式
  • 参数化工厂方法
  • 使用模板来避免子类
  • 命名惯例

相关模式

  • 抽象工厂通常用工厂方法实现
  • 工厂方法在模板方法中被调用
  • 原型不需要子类创造者

原型 Prototype

意图:使用原型实例具体说明对象的类型,通过复制原型来创建对象

应用

  • 运行时指明要实例化的对象
  • 避免构造和类产品层次结构平行的工厂
  • 产品的实例只有几种不同的状态组合

原型

协作:客户要求一个原型复制自己

后果

  • 运行时添加和移除产品
  • 通过变化值指定新对象
  • 减少子类
  • 动态配置应用

实现

  • 使用原型管理器
  • 实现 Clone 操作
  • 初始化克隆体

相关模式:原型和抽象工厂是竞争的,但也可以一起使用

单例 Singleton

意图:确保一个类只有一个实例,提供一个全局点访问它

应用:只能有一个类,并必须通过一个众所周知的访问点访问

单例

协作:客户仅通过单例的 Instance 操作访问单例实例

后果

  • 控制对单独实例的访问
  • 减少命名空间
  • 灵活

实现

  • 确保唯一实例
  • 单例类的子类

相关模式:前面的几个都可以使用单例模式实现

结构模式

适配器 Adapter

意图:将一个类的接口转化为客户需要的接口

应用

  • 想要使用现存的类,但接口不匹配
  • 想要一个可以和其他无关或未预见到的类协同的类

适配器

后果:

类适配器

  • 通过一个具体适配器类适配
  • 只引入一个对象

对象适配器

  • 让一个适配器和多个被适配者工作
  • 难以重载被适配者的行为

实现

  • 从目标 public 继承而从被适配者 private 继承
  • 可插拔适配器
    • 抽象操作
    • 指派对象
    • 参数化适配器

相关模式

  • 与桥类似,但是桥是为了分离接口与实现,而适配器是为了改变现存的对象的接口
  • 修饰器不改变接口
  • 代理则充当代理人的角色

桥 Bridge

意图:将抽象从实现中解耦合

应用

  • 避免抽象和实现的绑定
  • 改变实现对客户没有影响
  • 完全隐藏抽象的实现

桥

协作:抽象将客户请求转发给实现对象

后果

  • 解耦合接口和实现
  • 增强扩展性
  • 对客户隐藏实现细节

实现

  • 只有一个实现者时也可以使用
  • 创建正确的实现对象
  • 共享实现者

相关模式

  • 抽象工厂可以创建并配置一个特定的桥
  • 适配器通常是马后炮,而桥则出现在设计之前

组合 Composite

意图:将对象组合为树形结构来表示部分-整体层次结构。可以一致地处理单独的对象和对象的组合

应用

  • 想要表示部分-整体的对象层次结构
  • 想要忽略单独的对象和对象的组合的区别

组合

组合对象

协作:客户使用组合类接口与对象交互。如果接受者是叶,则直接处理请求;如果是组合,则转发请求到子组件

后果

  • 由原始对象和组合对象定义了类的层次结构
  • 使客户端更简单
  • 添加新组件更容易
  • 使设计过于一般

实现

  • 显式父引用
  • 共享组件
  • 最小化组件接口
  • 声明子组件操作

相关模式

  • 组件-父亲链接通常用于责任链
  • 修饰器通常和组合一起使用
  • 享元可以共享组件,但是不能够引用父亲
  • 迭代器可以用于遍历组合

修饰器 Decorator

意图:动态给一个对象附加责任。相比子类,修饰器扩展功能更灵活

应用

  • 动态、透明地给个别对象添加责任

修饰器

协作:将请求转发给组件对象,可能在请求前后添加操作

后果

  • 比静态的继承更灵活
  • 避免了继承中的功能负担
  • 修饰器和组件不相同
  • 很多小对象

实现

  • 接口一致
  • 忽略抽象修饰器类
  • 保持组件类轻量
  • 换皮 vs 换本

相关模式

  • 适配器:改变了接口
  • 相当于只有一个对象的组合
  • 策略改变了本质,修饰器改变了表面

外观 Facade

意图:提供子系统中的接口集合的统一接口

应用

  • 想要给一个复杂的系统提供一个简单的接口
  • 客户和抽象的实现类有很多依赖
  • 给系统分层

外观

协作:客户端不能直接访问子系统对象,只能通过给外观发送请求来与子系统通信

后果

  • 对客户隐藏了子系统组件
  • 提高了子系统和客户的弱耦合

实现

  • 减少客户-子系统的耦合
  • 公共 vs 私有子系统类:子系统封装了类,类封装了状态和操作

相关模式

  • 抽象工厂可以和外观一起使用来提供创建子系统对象
  • 中介者与外观类似,但目的是抽象同事之间的通信
  • 外观对象通常是单例的

享元 Flyweight

意图:使用共享来高效支持大量细粒度的对象

应用

  • 需要大量对象
  • 存储花费高
  • 大量对象状态可以放到外部
  • 一旦外部状态被移除,许多对象组可以被较少的共享对象替代
  • 应用不依赖于对象实体

享元

共享享元

协作

  • 享元内部的状态存在具体享元对象中,外部状态存储或由客户对象计算,客户在使用享元的操作时可以将这状态传递给享元
  • 客户不应该直接实例化具体享元

后果:增加了运行时间,但减少了空间

实现

  • 移除外部状态
  • 管理共享对象

相关模式

  • 和组合一起来创建一个有向无环图
  • 最好将状态和策略对象实现为享元

代理 Proxy

意图:通过控制对象访问的代理人

应用

  • 远程代理提供不同地址空间的对象的本地表示
  • 虚拟代理按需创造昂贵的对象
  • 保护代理控制对原始对象的访问
  • 智能引用是对普通指针的替代

代理

运行时代理

后果

  • 远程代理可以隐藏对象在一个不同地址空间的事实
  • 虚拟代理可以按需创建对象来执行优化
  • 保护代理和智能引用运行额外的内务任务

实现

  • 重载 ->
  • 并不总是知道实际对象的类型

相关模式

  • 适配器提供的是不同的接口
  • 代理控制对一个对象的访问,而修饰器添加对象的职责

表现模式

责任链 Chain of Responsibility

意图:通过给予多个对象处理请求的机会来避免将请求的发送者耦合到接收者。

应用:

  • 多个对象可能处理请求
  • 不显示指明接收者来对多个对象中的一个发送请求
  • 处理请求的对象集合应该动态指明

责任链

责任链对象结构

协作:当客户发起请求,请求通过链传导直到一个具体的处理者处理它

后果

  • 减少耦合
  • 添加给对象分配责任的灵活性
  • 收据不保证

实现

  • 实现继承链
    • 定义新链
    • 使用现成链
  • 连接继承者
  • 表示请求

相关模式:通常和组合一起使用,即一个组件的父亲可以是其继承者

命令 Command

意图:将请求封装为对象,使得能够用不同请求来参数化客户

应用

  • 通过执行的动作来初始化对象
  • 支持撤销
  • 基于原始操作构建高级操作系统

命令

协作

  • 客户创建具体命令对象并指明接收者
  • 调用者对象存储具体命令对象
  • 调用者通过调用对命令调用 Execute 来发起请求
  • 具体命令对象调用它的接收者的操作来处理请求

命令协作

后果

  • 从直到如果执行命令的一些处解耦合了操作
  • 命令是第一类对象
  • 可以将命令组装为一个组合命令
  • 添加新命令很容易

实现

  • 命令应该多智能
  • 支持撤销和重做
  • 避免撤销过程中的错误积累
  • 使用 C++ 模板

相关模式

  • 组合可以用于实现大型命令
  • 备忘录可以保持需要撤销影响的命令的状态
  • 在放到历史列表中必须复制的命令作为一个原型

解释器 Interpreter

意图:给定语言,用一个解释器为其语法定义表示

应用

  • 语法简单
  • 效率不是关键点

解释器

协作

  • 客户用抽象语法树建立句子
  • 每个非终止结点解释

后果

  • 容易改变和扩展语法
  • 实现语法很容易
  • 复杂的语法很难维护
  • 添加新的解释表达式的方法

实现

  • 创建抽象语法树
  • 定义解释操作
  • 用享元共享终止符

相关模式

  • 抽象语法树是组合模式的一个实例
  • 终止符是享元
  • 使用迭代器来遍历结构

迭代器 Iterator

意图:在不暴露底层表示的情况下提供顺序访问聚合对象的方法

应用

  • 不暴露内部表示的情况下访问聚合对象内容
  • 提供多重遍历聚合对象的方法
  • 提供遍历不同聚合结构的一致接口

迭代器

协作:具体迭代器跟踪聚合中的当前对象并计算遍历中的下一个对象

后果

  • 支持聚合的遍历过程中更改
  • 简化聚合接口
  • 多种遍历方式

实现

  • 谁控制迭代
    • 外部迭代器
    • 内部迭代器
  • 迭代器是否健壮,即是否支持在遍历过程中添加或删除对象

相关模式

  • 通常应用于递归结构,如组合
  • 依赖于工厂方法来实例化迭代器子类
  • 使用备忘录捕捉迭代状态

中介者 Mediator

意图:定义一个封装了一个集合的对象交互方法的对象,通过防止对象直接与彼此交互

应用

  • 集合的对象通信复杂
  • 因为引用到和通信了太多对象而复用对象困难

中介者

中介者对象结构

协作:同事通过中介者对象发送和接收请求

后果

  • 限制子类
  • 解耦合同事
  • 简化对象协议
  • 抽象对象协作
  • 中心化控制

实现

  • 忽略抽象中介者类
  • 同事-中介者通信

相关模式:外观抽象了子系统的对象来提供一个更方便的接口,是单向的;而中介者方便了同事对象的协作表现,是双向的

备忘录 Memento

意图:在不违反封装的前提下,捕获并外部化对象的内部状态,使得对象可以在一会儿恢复状态

应用

  • 一个对象的状态必须保存以恢复状态
  • 获取状态的直接接口会暴露实现细节

备忘录

协作

  • 保管者从发起人请求一个备忘录,保存一段时间,然后将其交还给发起人

备忘录协作

  • 备忘录是被动的,只有创建一份备忘录的发起人可以赋值或获取状态

后果

  • 保持封装边界
  • 简化发起人
  • 使用备忘录可能昂贵
  • 保管备忘录的代价

实现

  • 狭窄的公共接口:将 Originator 设为友元,其余内容全为 private
  • 存储增量改变

相关模式

  • 命令可以使用备忘录来撤销操作
  • 迭代器

观察者 Observer

意图:定义对象之间一对多的依赖,使得当一个对象改变状态时,所有的依赖都被通知并自动更新

应用

  • 一个依赖于另一个
  • 一个改变,其余的也会改变,但不知道多少对象需要改变
  • 需要通知其他对象,但不知道是什么

观察者

协作

  • 当改变发生时,具体主体通知观察者
  • 具体观察者可能向主体询问信息

观察者协作

后果

  • 抽象主体和观察者之间的耦合
  • 支持广播通信

实现

  • 将主体映射到观察者
  • 观察超过一个对象
  • 谁发送更新
    • 当状态改变时,主体自动发送通知
    • 客户手动通知
  • 悬垂指针
  • 确保主体状态在通知之前是自我一致的
  • 避免观察者指明的更新协议:推送和拉取模型
  • 使用改变管理器封装复杂的更新语法

相关模式

  • 改变管理器充当主体和观察者的中介者
  • 改变管理器使用单例

状态 State

意图:允许内部状态改变后,对象改变其行为,即改变了它的类

应用

  • 对象的行为依赖于状态,必须在运行时改变行为
  • 操作有依赖于状态的大型,多部分的条件语句

状态

协作

  • 上下文指派特殊状态的请求给当前具体状态对象
  • 上下文可以传递自己为参数
  • 上下文和具体状态子类都可以决定下一个状态是什么

后果

  • 局部化状态特定的行为并划分为不同状态
  • 使状态转移显式
  • 状态对象可以共享

实现

  • 谁定义状态转移
  • 基于表的替代
  • 创建并销毁状态对象
  • 使用动态继承

相关模式

  • 享元解释状态共享的问题
  • 状态对象通常是单例

策略 Strategy

意图:定义算法家族,封装每一个,使其可以交换

应用

  • 许多相关的类只在表现不同
  • 需要一个算法的不同变式
  • 使用客户不应该知道的数据的算法
  • 类定义了许多表现,出现其操作的多重条件语句中

策略

协作

  • 处理和上下文交互以实现选中的算法
  • 上下文从其客户转发请求到策略

后果

  • 相关的算法家族
  • 子类的替代
  • 策略减少条件语句
  • 实现的选择
  • 客户必须了解不同的策略
  • 策略和上下文之间通信的开销
  • 增加对象数量

实现

  • 定义策略和上下文接口
  • 策略作为模板参数

相关模式:策略对象通常是好的享元

模板方法 Template Method

意图:在操作中定义算法的骨架,将一些步骤延迟到子类,即让子类在不改变算法结构的前提下重新定义了算法的特定步骤

应用

  • 实现算法中不变的部分,让子类实现会变的部分
  • 子类中共同的表现重构以避免代码重复
  • 控制子类扩展

模板方法

协作:具体类依赖于抽象类实现算法中不变的部分

实现

  • 最小化原始操作
  • 命名惯例:Do-

相关模式:

  • 工厂模式通常被模板方法调用
  • 模板方法使用继承来改变算法的部分,策略使用指派来改变整个算法

访问者 Visitor

意图:表示对一个对象结构中的元素的执行的操作。可以在不改变操作的元素的类的情况下定义新的操作

应用

  • 拥有不同接口的需要类的对象的结构,想要基于具体的类执行操作
  • 在一个对象结构中有许多不同且无关的操作需要执行,但不想要用这些操作污染类
  • 定义对象结构的类很少改变,但在此之上经常定义新操作

访问者

协作

  • 客户创建一个具体访问者对象并遍历对象结构,使用访问者访问每个元素
  • 当每个元素被访问时,调用相关类的访问者操作

访问者协作

后果

  • 添加新操作容易
  • 收集相关的操作并分离无关的操作
  • 添加新具体元素类很困难
  • 积累状态

相关模式

  • 常应用于组合
  • 可以用于解释器