附录 A · 速查表与易混对比
这一附录是给翻查用的,可以单独打印。A.1 把 23 个模式各压成一行:它是什么、什么时候用、结构上抓哪一点、去哪一章看。A.2 把各章“容易混淆的模式”里的判别问题收在一张表里:两个模式拿不准时,回答那一个问题就能分开。星级和一句话与第 1 章总表一致;“什么时候用”一列的说法与第 28 章的决策表一致,要看某个模式什么时候别用,翻对应章的“什么时候别用”一节。
A.1 23 个模式速查
Section titled “A.1 23 个模式速查”星级是本书作者的判断,依据是模式在主流框架和日常业务代码里出现的频率:★★★ 几乎每个项目都会碰到,★★ 在特定类型的系统里常见,★ 用得少,认得出即可。
创建型:对象怎么来
| 模式 | 星级 | 一句话 | 什么时候用 | 结构要点 | 章 |
|---|---|---|---|---|---|
| 工厂方法(Factory Method) | ★★★ | 让子类决定创建哪种对象 | 按类型 new 对象的 if 抄在好几个调用点,加一种类型每处都要改 |
创建者在固定流程里调工厂方法,具体创建者子类决定返回哪种产品 | 3 |
| 抽象工厂(Abstract Factory) | ★★ | 成套创建一组相互配合的对象 | 几个对象必须配套,各处各自 if 出来,配错了也不报错 |
一个工厂有多个创建方法,每个具体工厂产出同一族的一整套产品 | 4 |
| 建造者(Builder) | ★★★ | 分步骤组装一个参数很多的对象 | 构造函数参数越加越多,靠位置对应,传错位置不报错 | 按名字一步步设值,build() 时统一校验,产品造好不再变 |
5 |
| 单例(Singleton) | ★★★ | 保证一个类全局只有一个实例 | 创建一次很贵的对象被每个调用点各造一份 | 类自己用字段保存唯一实例,instance() 第一次调用时创建 |
6 |
| 原型(Prototype) | ★ | 复制一个现有对象来得到新对象 | 要一个和现成对象差不多的新对象,逐字段复制总漏字段 | 对象自己实现 clone(),决定每个字段怎么复制 |
7 |
结构型:对象怎么组合
| 模式 | 星级 | 一句话 | 什么时候用 | 结构要点 | 章 |
|---|---|---|---|---|---|
| 适配器(Adapter) | ★★★ | 把一个接口转换成调用方期望的接口 | 第三方接口和你要的对不上,转换代码散在每个调用处 | 适配器实现目标接口,内部持有被适配对象并转调 | 9 |
| 装饰器(Decorator) | ★★★ | 在不改原对象的前提下层层叠加功能 | 想给对象叠加压缩、加密这类功能,按组合写子类,数量成倍长 | 装饰器实现与被包对象相同的接口,同时持有一个该接口的对象 | 10 |
| 外观(Facade) | ★★★ | 给一组复杂的子系统提供一个简单入口 | 同一套“按顺序调几个子系统、失败回滚”的流程抄在几个入口 | 外观新定义一个更简单的方法,只有外观调子系统,反过来不行 | 11 |
| 代理(Proxy) | ★★★ | 用一个替身控制对真实对象的访问 | 权限、懒加载、审计这些把关的事散在调用处 | 代理与真实对象同接口并持有它,决定转不转、什么时候转 | 12 |
| 组合(Composite) | ★★ | 把对象组织成树,单个和整体一样对待 | 单个和一组要一样对待,组里还能再套组 | 叶子和容器实现同一个接口,容器持有一组该接口的孩子 | 13 |
| 桥接(Bridge) | ★ | 把两个独立变化的维度拆开再组合 | 两个维度各自会变,子类数是两边相乘 | 抽象部分持有实现部分的接口,两边各有自己的继承体系 | 14 |
| 享元(Flyweight) | ★ | 共享大量重复的细粒度对象以省内存 | 大量对象各带一份完全相同的数据 | 享元工厂按键缓存只读的内在状态,外在状态由使用者自己保存 | 15 |
行为型:对象怎么协作
| 模式 | 星级 | 一句话 | 什么时候用 | 结构要点 | 章 |
|---|---|---|---|---|---|
| 策略(Strategy) | ★★★ | 把一族可互换的算法各自封装 | 一族可互换的算法写成一串 if,好几处都要选 |
上下文持有一个策略接口,由外面挑好具体策略交给它 | 17 |
| 观察者(Observer) | ★★★ | 一个对象变化时自动通知所有关注者 | 一件事之后要做的事越来越多,全写在一个函数里 | 主题保存订阅者列表,发生变化时逐个通知,订阅者互不知道 | 18 |
| 命令(Command) | ★★★ | 把一个请求封装成对象 | 操作要撤销、排队、定时、记日志 | 命令存下请求参数和撤销所需信息,调用者决定何时执行或撤销 | 19 |
| 模板方法(Template Method) | ★★★ | 父类定流程骨架,子类填具体步骤 | 几段流程只有中间一两步不同,各抄了一份 | 父类的模板方法固定步骤顺序,子类只实现抽象步骤或覆写钩子 | 20 |
| 状态(State) | ★★★ | 对象的行为随内部状态切换 | 每个方法里都有 if status == …,加一个状态要翻遍 |
上下文把操作交给当前状态,具体状态做完事决定转到哪个状态 | 21 |
| 迭代器(Iterator) | ★★★ | 不暴露内部结构地逐个访问元素 | 翻页、游标写在每个调用方的循环里 | 聚合对象创建迭代器,迭代器记住走到哪里,只交出下一个 | 22 |
| 责任链(Chain of Responsibility) | ★★ | 请求沿一条处理者链传递 | 谁来处理写成一串 if,请求要依次过几道关 |
每个处理者持有下一个,自己决定处理还是交下去,调用方只认链头 | 23 |
| 中介者(Mediator) | ★★ | 用一个中间对象协调多个对象的交互 | 几个对象你调我、我调你,加一个要改好几个类 | 同事只持有中介者并通知它,中介者认识每个同事、写着联动规则 | 24 |
| 备忘录(Memento) | ★ | 保存并恢复对象的内部状态 | 要把对象恢复成之前的样子,字段由外面一个个抄出去 | 发起人创建并读回快照,负责人只保管,不读不改 | 25 |
| 访问者(Visitor) | ★ | 不改元素类就给它们加新操作 | 节点类型不常变,报表和操作不断加 | 元素只有 accept,访问者为每种元素各写一个 visit 方法 |
26 |
| 解释器(Interpreter) | ★ | 为一种简单语言定义文法并解释执行 | 规则要让运营自己写成一行文字 | 每条文法规则一个类,终结符直接读上下文,非终结符组合子表达式 | 27 |
A.2 易混对比
Section titled “A.2 易混对比”每一行是两个(或几个)看着像的模式,加上各章给出的那个判别问题。一对模式在两章里都出现时只列一次,写出两个章号;只在一章出现的,写那一章。
创建型之间
| 容易混的模式 | 一个问题就能分清 | 章 |
|---|---|---|
| 简单工厂、工厂方法、抽象工厂 | 是一个函数,一个方法加一组子类,还是一个对象负责一整套产品 | 3、4 |
| 工厂方法、抽象工厂、建造者 | 造一个,造一套,还是分好几步造一个复杂的 | 4、5 |
| 建造者、原型 | 从零一项项填,还是从一个已经配好的对象出发、复制后只改几处 | 7 |
| 工厂方法、原型 | 是否要为每种产品准备一个子类;原型手里有一个现成对象就能造出同类新对象 | 7 |
| 单例、全局变量、模块级实例、依赖注入 | 谁创建、什么时候创建;用的人是到处直接拿,还是必须从参数里接 | 6 |
| 原型、备忘录 | 复制出来的东西给谁用:接着改的新对象,还是只交回 restore() 的快照 |
7、25 |
结构型之间
| 容易混的模式 | 一个问题就能分清 | 章 |
|---|---|---|
| 适配器、装饰器、外观、桥接 | 为什么包、对外接口变不变:适配器换接口,装饰器加功能,外观简化入口,桥接是事先拆开而不是事后补救 | 9、10、11、14 |
| 适配器、代理 | 接口换不换:适配器换接口,代理不换 | 12 |
| 装饰器、代理 | 去掉它,功能少了一样(装饰器),还是功能一样、只是访问没人把关了(代理) | 10、12 |
| 装饰器、责任链 | 每一层都一定往下传吗 | 10、23 |
| 装饰器、组合 | 手里有几个孩子、套这一层为了什么:只包一个对象为加功能,还是一组孩子为让一组当一个用 | 13 |
| 外观、适配器 | 接口是谁定的、背后有几个对象:调用方先定好接口去对上它是适配器,替调用方新写更省事的方法是外观 | 11 |
| 外观、代理 | 外观定义新的、更简单的接口,背后是好几个对象;代理与原对象同接口,背后是一个对象 | 12 |
| 外观、中介者 | 删除测试:删掉它,子系统要不要改 | 11、24 |
| 桥接、策略 | 被持有的接口有几个方法,持有它的一方有没有自己的子类 | 14、17 |
| 享元、单例 | 整个类只有一个,还是每种一个、按需共享 | 15 |
| 享元、对象池 | 对象可变、同一时间借给一个使用者,还是同时被许多使用者引用、因此必须不可变 | 15 |
| 享元、缓存 | 为了省时间(可淘汰、淘汰了重算),还是为了省内存(大家用同一份) | 15 |
| 组合、迭代器 | 要的是一个个节点,还是一个答案 | 13、22 |
行为型之间
| 容易混的模式 | 一个问题就能分清 | 章 |
|---|---|---|
| 策略、状态 | 谁来换:外面挑好交进去的是策略,状态做完事自己切到下一个的是状态 | 17、21 |
| 策略、模板方法 | 换算法要不要新建子类 | 17、20 |
| 策略、命令 | 这个对象会不会被存起来以后再用 | 19 |
| 模板方法、工厂方法 | 子类填的那一步是不是在创建对象 | 20 |
| 状态类、表驱动状态机 | 每一步除了换状态,还要做多少事 | 21 |
| 观察者、中介者 | 中间那个对象里有没有业务规则 | 18、24 |
| 观察者、发布订阅 | publish 返回时,订阅者跑完了没有(有没有消息代理) |
18 |
| 命令、备忘录 | 撤销时靠存下来的状态,还是靠反向操作 | 19、25 |
| 命令、责任链 | 关心的是请求长什么样,还是谁来接 | 23 |
| 迭代器、访问者 | 对每个节点做的事,要不要按节点类型分开写 | 22、26 |
| 组合、访问者 | 以后加得多的是节点还是操作 | 26 |
| 组合、解释器 | 这棵树是不是一句话 | 27 |
| 解释器、访问者 | 操作多不多 | 27 |
A.3 使用提示
Section titled “A.3 使用提示”- 先在 A.1 的“什么时候用”一列找到你的麻烦,再翻到那一章的“什么时候别用”一节,确认没有踩进去。
- 两个模式看着像,先看 A.2 的问题能不能当场回答;回答不了,就按那一行的章号回去看对应的案例。
- 同一个麻烦如果能用一个
if、一个函数或一张映射表说完,先别上模式。第 28.5 节和第 29.8 节把这条讲得更完整。