《设计模式:可复用面向对象软件的基础》读书笔记
GoF(Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides)1995 年经典,俗称”设计模式”或 GoF 书。本书确立了 23 个经典设计模式的目录(catalog),并给出描述模式的标准框架。全文字版 epub 已完整消化,本笔记按”两大设计原则 → 模式描述框架 → 23 模式目录 → 选型与使用 → 案例研究”组织。
一、两大核心设计原则(全书之魂)
这两条原则贯穿全部 23 个模式,比任何单个模式都重要:
-
针对接口编程,而不是针对实现编程(Program to an interface, not an implementation)
- 区分”类”与”类型”:类定义实现(内部状态+操作实现),类型只定义接口(可响应的请求集合)。一个对象可有多个类型,不同类的对象可有同一类型。
- 区分”类继承”与”接口继承(子类型化)“:类继承是代码/表示的复用机制;接口继承描述”一个对象可替代另一个对象”的时机。
- 实践含义:变量不要声明为具体类的实例,只承诺抽象类定义的接口。这极大减少子系统间的实现依赖。
- 创建型模式(Abstract Factory / Builder / Factory Method / Prototype / Singleton)正是为了在实例化时刻把接口与实现透明地关联起来,保证系统”以接口而非实现来书写”。
-
优先使用对象组合,而不是类继承(Favor object composition over class inheritance)
- 类继承 = 白盒复用:父类内部对子类可见;编译期静态定义;“继承破坏封装”——子类实现与父类实现强绑定,父类一改子类就得跟。
- 对象组合 = 黑盒复用:只通过接口访问,封装不被破坏;运行期动态可替换;类保持小而专注,不会长成不可管理的巨兽。
- 代价:基于组合的设计对象数量更多(类更少),系统行为散布在对象协作关系中而非定义在单个类里。
- 折中方案:只从抽象类继承(几乎不提供实现),或委托(Delegation)——组合+继承的结合,把部分职责委托给关联对象,比继承更灵活但可能增加对象数量。
二、模式的描述框架(9 要素)
本书为每个模式建立了标准描述模板,这也是”模式语言”这一表达形式的开创性贡献:
- Pattern Name and Classification(模式名与分类)
- Intent(意图)— 一句话说明模式做什么、解决什么问题
- Also Known As(别名)
- Motivation(动机)— 问题与解法的示意场景
- Applicability(适用性)— 何时使用该模式,何时不该用
- Structure / Participants / Collaborations(结构/参与者/协作)— UML 图示
- Consequences(效果)— 权衡取舍,评价方案的关键部分
- Implementation(实现)— 实现难点与技巧
- Sample Code / Known Uses / Related Patterns(样例代码/已知应用/相关模式)
三、分类体系:目的 × 范围
二维分类(Table 1.1):
- 目的(purpose):创建型(对象创建过程)/ 结构型(类或对象的组合)/ 行为型(类或对象的交互与职责分配)
- 范围(scope):类模式(类与子类关系,靠继承,编译期静态)/ 对象模式(对象间关系,运行期动态)。绝大多数模式属于对象范围。
四、23 个模式目录(按意图浓缩)
创建型(Creational,5 个)
| 模式 | 意图一句话 |
|---|---|
| Abstract Factory | 提供创建”相关对象家族”的接口,不指定具体类 |
| Builder | 将复杂对象构造与其表示分离,同一构造过程可产生不同表示 |
| Factory Method | 定义创建对象的接口,让子类决定实例化哪个类(延迟到子类) |
| Prototype | 用原型实例指定待创建对象的种类,通过复制原型创建新对象 |
| Singleton | 保证一个类只有一个实例,并提供全局访问点 |
结构型(Structural,7 个)
| 模式 | 意图一句话 |
|---|---|
| Adapter | 把一个类的接口转换成客户端期望的另一个接口,让不兼容的类协作 |
| Bridge | 将抽象与其实现解耦,使二者可独立变化 |
| Composite | 把对象组合成树形”部分-整体”层次,客户端统一对待单个对象与组合 |
| Decorator | 动态给对象附加额外职责,是生成子类扩展功能的灵活替代 |
| Facade | 为子系统的一组接口提供统一的高层接口,使子系统更易用 |
| Flyweight | 用共享技术高效支持大量细粒度对象 |
| Proxy | 为另一对象提供替身/占位符以控制访问 |
行为型(Behavioral,11 个)
| 模式 | 意图一句话 |
|---|---|
| Chain of Responsibility | 请求沿接收者链传递,直到有对象处理,解耦发送者与接收者 |
| Command | 把请求封装为对象,支持参数化、排队、日志与可撤销操作 |
| Interpreter | 给定语言,定义其文法表示并用解释器解释句子 |
| Iterator | 顺序访问聚合元素而不暴露其内部表示 |
| Mediator | 用一个对象封装一组对象的交互方式,促进松散耦合 |
| Memento | 不破坏封装地捕获并外部化对象内部状态,以便日后恢复 |
| Observer | 定义一对多依赖,一个对象状态变化时所有依赖者自动获通知并更新 |
| State | 对象内部状态改变时改变其行为,看起来像改变了类 |
| Strategy | 定义算法族并各自封装、可互换,算法与使用方独立变化 |
| Template Method | 在操作里定义算法骨架,把某些步骤延迟到子类 |
| Visitor | 表示作用于对象结构元素上的操作,可在不改变元素类的前提下定义新操作 |
五、模式如何解决问题 & 如何选用
设计模式解决的核心反复出现的问题(第 1 章):
- 找到合适的对象:靠领域词汇分析(名词→候选对象)与职责 CRC 法。
- 决定对象粒度:粗粒度系统更小更快但难定制;模式覆盖从 Strategy(细)到框架/ MVC(粗)的粒度谱。
- 指定对象接口与指定对象实现:接口与实现分离后,可为不同场景换实现而不动客户代码。
- 运行期与编译期结构:继承是编译期的、僵化的;动态组合是运行期的。对象图很少与类图一致。
- 为变化而设计:封装变化点。应用(application)、工具箱(toolkit)、框架(framework)是复用的三个层次——框架把应用的不变方面固化下来。
如何选择一个模式(1.7):按名字与意图浏览目录 → 查看相关模式并比较 → 检查模式在目标上下文中是否可行 → 简化假设/替换等价模式(如 Strategy 与 State 结构相似;Mediator/Strategy 常可用 Observer 实现)。
如何使用一个模式(1.8):确认适用性 → 从恰当名字开始命名参与方 → 声明接口 → 决定关键机制(先写伪码)→ 把设计分解进各参与方 → 评估并迭代权衡。
六、案例研究:文档编辑器 TexStar(第 2 章)
用一个贯穿案例演示模式如何自然浮现:
- 文档结构:文档 = Component 递归组合(Composite 模式),字形 Glyph 作为最小叶子单元。
- 排版:把排版算法封装为 Compositor,Strategy 模式使排版策略可替换。
- 界面装饰:Border/ScrollBox 用 Decorator 给视图加边框/滚动条,避免类组合爆炸。
- 格式化与规格:Spec 对象记录文档生成规格(Memento/Command 支撑撤销)。
- 用户操作:菜单/快捷键触发命令 → Command 模式支持撤销;Glyph 遍历 → Visitor;用户交互流状态 → State;依赖更新 → Observer;字形共享 → Flyweight。
- 结论:同一系统在十余个模式上交织,模式之间是协作关系而非孤立选项(如 Composite 常配 Iterator/Visitor;Prototype 常是 Abstract Factory 的替代)。
七、可行动点
- 代码评审时对照两条原则提问:这里的变量声明成具体类了吗?这里的继承是”替代关系”还是”偷实现”?
- 见到 if/switch 按类型分派 → 考虑 State / Strategy;见到一大坨一次性构造 → Builder;见到子系统散落调用 → Facade。
- 学习顺序建议:先吃透 Strategy/Observer/Composite/Adapter/Decorator 五个高频模式,再按目录补全。
- 本书示例基于 C++/Smalltalk;现代语言(Java/Python/C#)中部分模式的必要性下降(闭包替代 Command/Strategy 的一部分、语言内置单例机制、动态语言弱化 Iterator),但意图层面仍全部有效——评价”某模式已过时”时先区分是”意图”还是”某个语言的样板写法”过时。
关联
- 《Effective C++》-第三版-55个改善程序与设计的具体做法-Scott-Meyers — 条目 33/35/39(替代继承改用组合、避免派生类接口扩展)与本书原则二互为印证
- 《Clean Code》-代码整洁之道-Robert-C-Martin — SRP/OCP 等 SOLID 原则是模式背后的原则基础
- 《重构》-改善既有代码的设计 — 重构手法常以”引入某模式”为落点
- 课程总复习-设计模式 — 同名课程的速查索引