# 设计模式是一套共享的词汇

经典设计模式速成课。Gang of Four 目录中的 20 个模式——Factory、Builder、Adapter、Decorator、Strategy、Observer、State 等——每个都有英文名称、一幅图示、一张「问题 → 思路 → 例子 → 代价」卡片,以及它与相邻模式的关联。结尾附一页纸的全部模式速查表。

- **Author:** Fedor Chishchin
- **Level:** intro
- **Reading time:** ~30 分钟阅读
- **Published:** 2026-06-02
- **Source:** https://fedorthinks.com/zh/learn/design-patterns-express

---

## 1. 什么是模式

设计模式是对一个在代码里一再出现的问题的、有名字的可复用答案。它是词汇,不是库——你不去安装它,你认出它。

### Design Pattern——给你早已会做的那一步起的名字

> 球员不会在比赛中途现场发明「撞墙配合」。它有个名字,而说出这个名字,比把整套动作描述一遍要快。

**设计模式**是称职的开发者无论如何都会徒手重新发明的一步。给它起个名字,就把一段解释变成了全队共享的一个词。当有人说「用 Decorator 包一层」,所有人立刻看到那个形状——**这就是模式全部的意义:一套共享的词汇**,用来指称你本来就会做的那些步骤。

#### Design Pattern (设计模式)

- **是什么:** 对代码中反复出现的问题的、有名字的解法。
- **不是什么:** 不是库:你不去安装它,你认出它,然后自己写。
- **尺度:** 活在单个模块的代码里——不同于架构模式,后者决定整个系统的平面图。
- **为什么:** 共享语言:一个词代替一段话。

### Gang of Four——模式是被发现的,不是被发明的

> 没有人发明拱。建造者们一再发现它是跨越空隙最牢固的办法——于是它有了名字。

**Gang of Four(GoF,四人组)**是《Design Patterns》一书的四位作者。他们并没有设计出 23 个巧妙的花招;他们是把好代码里一再出现的解法**编成了目录**。所以模式不是要去命中的目标——它是问题需要时你自然而然走到的形状。**如果你是硬塞进去的,多半是你还没遇到那个问题。**

### 地图:三个家族,三个问题

> 厨房里有三类活的工具:做菜、装盘、上菜。模式也是这样划分的。

**创建型**模式讲的是对象怎么诞生——谁来构造它们,怎么构造。**结构型**模式讲的是对象怎么拼成更大的形状。**行为型**模式讲的是对象怎么对话、怎么分担责任。几乎每个经典模式都是这三个答案之一。

_Diagram: 三个家族和本课的 20 个模式——第 2–5 章依此展开。_

### Indirection——你用不着的模式只是复杂度

> 口袋里的瑞士军刀很顺手。把二十件工具全拧到你家大门上,就不然了。

每个模式都会增加**间接层(indirection)**——你和那个东西之间多出一层。当它换来的灵活性你真会用到时,这值得;不会用到时,就是纯粹的代价。常见的错误不是不懂模式,而是**在问题还不存在时就伸手去拿一个。**

### 怎么读每个模式

> 护照:每本的栏目都一样,所以很容易比较。

从这里开始,每个模式都有相同的几个部分:它的**英文名称**(那就是术语——记住它)、一幅取自日常生活的画面、一张**图示**,以及一张带栏目的**卡片**:_问题_(哪里疼)、_思路_(怎么解决)、_例子_、_代价_(你付出什么)、_何时使用_,以及*关联*模式。结尾有一张把所有名称放在一页上的速查表。

> **在给模式命名之前:** 「我真的有它所解决的那个反复出现的问题吗——还是我只是喜欢这个词?」

> 模式是对话的捷径。用它是为了被人听懂,不是为了显得聪明。

## 2. 创建型模式

对象如何诞生——而不必把 `new` 撒得到处都是,也不把你的代码焊死在具体的类上。

### Factory——按名字要东西,而不是按配方

> 咖啡机:你按下「拿铁」,就得到一杯。你不用自己研磨、打奶泡、倒——也不需要知道它是怎么做的。

**Factory(工厂)**把对象交给你,而不用你直接调用它的构造函数,所以调用方的代码从不依赖具体的类。它只认识**接口**。**它换来的是自由:可以改变被构造的东西,而不必去动提出请求的那一方。**

_Diagram: 客户端按名字请求;工厂选择类;客户端只看得到 PaymentProvider 接口。_

#### Factory (Factory Method, 工厂)

- **问题:** `new StripeProvider()` 散落在代码各处——被焊死在一个类上。
- **思路:** 向工厂要对象;由它决定构造哪个类。
- **例子:** `createPaymentProvider("stripe")` 返回一个 `PaymentProvider`。换成 PayPal,只需改工厂,不用改每一处调用。
- **代价:** 多一层。一个只构造一样东西的工厂是坏味道。
- **何时使用:** 有好几种实现,或者将来会有。
- **关联:** **Abstract Factory**——同一个思路,用于一整族对象。

### Abstract Factory——一次造出一整套相配的家族

> 买一套家具——「维多利亚风」或「现代风」——每一件都是同一种风格,不会有不配套的椅子。

当对象必须成**族**地保持一致时,**Abstract Factory(抽象工厂)**一次造出整套,让各部分彼此契合。很强大——但它是重型机械,只有当这些家族是真实存在的时候才值得。

_Diagram: 一个工厂,一套相配的东西:同一风格的按钮、菜单和对话框。_

#### Abstract Factory (抽象工厂)

- **问题:** 对象之间必须相配,而且很容易搞混。
- **思路:** 一个工厂以一种风格造出整套对象。
- **例子:** 一个 UI 工具包,用同一个工厂为 macOS 或 Windows 造出相配的按钮、菜单和对话框。
- **代价:** 重型机械:大量的接口和类。
- **何时使用:** 只有当家族是真实的,而且不止一个时。
- **关联:** 由 **Factory** 构成。「只服务一个产品的抽象工厂」是典型的过度设计。

### Builder——一步一步组装复杂对象

> 点一份定制汉堡——面包、肉饼、不要洋葱、加双份奶酪——一次选一样,最后说「好了」。

当一个对象有许多可选部分时,**Builder(建造者)**让你一块一块地设置,而不是用一个带十二个参数的怪物构造函数。最后调用 `build()`,拿到成品。**它把一个令人费解的构造函数换成了一条可读的链。**

_Diagram: 一串步骤 → build() → 一个成品对象。而不是一个你分不清哪个参数是哪个的构造函数。_

#### Builder (建造者)

- **问题:** 一个十二个参数的构造函数,其中一半是可选的。
- **思路:** 一次设置一个部分,最后 `build()`。
- **例子:** `QueryBuilder().select("name").where("age > 18").limit(10).build()`。
- **代价:** 真正的类旁边多出一个类。
- **何时使用:** 对象有许多可选部分。
- **关联:** **Factory** 决定构造_什么_;**Builder** 决定_怎样_组装复杂的东西。

### Singleton——有且只有一个,人人共用

> 一个国家有一位总统。所有提到「总统」的人指的都是同一个人——不会有两个。

**Singleton(单例)**保证只有一个实例和一个全局访问点。但要小心:单例是**穿着漂亮外套的全局状态**,而全局状态会让测试和推理变难。

_Diagram: 每个模块都拿到同一个实例——方便,但这是全局状态。_

#### Singleton (单例)

- **问题:** 你需要恰好一个共享实例。
- **思路:** 由类本身保证一个实例和一个访问点。
- **例子:** 所有人都从中读取的配置对象或日志器。
- **代价:** 全局状态:测试和推理都变得更难。
- **何时使用:** 很少。一个你**显式传入**的单一实例,往往胜过通过全局访问到的那个。
- **关联:** 和 **Factory** 一样属于创建型,但关心的是「几个」,而不是「哪一个」。

### Prototype——复制一份填好的,而不是从头新建

> 复印一张已经填好的表,比从空白表格从头填起要快。

当从零开始造一个对象既昂贵又琐碎时,**Prototype(原型)**让你把现有的对象**克隆**一份再稍作调整。适用于构造成本高、而且大多彼此相似的对象。

_Diagram: 一个配置好的对象被克隆并略加调整——而不是重新构造。_

#### Prototype (原型)

- **问题:** 从零构造这个对象既慢又琐碎。
- **思路:** 拿一个现成的对象,复制它,调整差异的部分。
- **例子:** 复制一个已配置好的游戏敌人,或一个带样式的文档节点。
- **代价:** 复制需要小心:哪些被复制,哪些仍是共享的。
- **何时使用:** 对象构造成本高,而且大多相似。
- **关联:** **Factory** 的一个替代方案:不是「构造」,而是「复制」。

> **创建型的问题:** 「谁来决定构造哪个具体的类——要改这个决定有多难?」

> 把 `new` 藏起来。知道对象怎么构造的地方越少,你改它就越自由。

## 3. 结构型模式

对象如何组合成更大的结构——把不兼容的部分拼在一起,不重写就能增加能力。

### Adapter——两个接口之间的转换插头

> 你的笔记本充电器到了国外——设备没问题,插座也没问题;只差中间那个小转换器。

**Adapter(适配器)**包装一个接口,让它看起来像另一个,使两个本来不是为了合作而设计的东西能一起工作。**这是你阻止别人的 API 渗透到整个代码库的办法。**

_Diagram: 你的代码调用 pay();适配器把它翻译成供应商的 charge()。_

#### Adapter (适配器, wrapper)

- **问题:** 别人的接口和你的对不上,而且在你的代码里四处蔓延。
- **思路:** 一个包装层,把调用从一个接口翻译到另一个接口。
- **例子:** 把第三方支付 SDK 包装起来,使它满足你自己的 `PaymentProvider`。
- **代价:** 供应商的 API 变化时,又多一层要更新。
- **何时使用:** 接入第三方代码或遗留代码。
- **关联:** 像 **Decorator** 和 **Proxy** 一样包装对象,但它会**改变接口**。

### Decorator——加配料,而不改披萨本身

> 一张普通披萨,再加奶酪,再加蘑菇——每一层都包住上一层并添加点什么,它仍然是披萨。

**Decorator(装饰器)**包装一个对象来添加行为,既不修改原对象,也不去继承它。接口保持不变,所以各层可以叠加。**它让你以没人需要事先预料的组合方式添加功能。**

_Diagram: 一个数据流被压缩包住,压缩又被加密包住。从外面看,它仍然是一个数据流。_

#### Decorator (装饰器, wrapper)

- **问题:** 你需要任意组合的附加功能;为每种组合写一个子类无法扩展。
- **思路:** 包住对象,添加一种行为,保持接口不变。
- **例子:** 数据流 → 压缩 → 加密;每一层都可以叠加或移除。
- **代价:** 许多小包装;一叠层层包裹更难调试。
- **何时使用:** 能力需要在运行时组合。
- **关联:** 和 **Proxy** 形状相同,但目的是添加,不是控制。**组合优于继承**的教科书式案例。

### Facade——杂乱后台前的一个简单前台

> 酒店礼宾员——你说「我需要一辆出租车和一个晚餐预订」,柜台后面那一团电话由他们去处理。

**Facade(外观)**为复杂的**子系统**提供一个简单、单一的入口,隐藏它那些活动部件。**它把一大片接口面缩成大多数调用方只需要的那一扇门。**

_Diagram: 一个 convert() 调用——背后是编解码器、缓冲区和音频流。_

#### Facade (外观)

- **问题:** 做一件简单的事,却要了解十几个子系统类。
- **思路:** 一扇简单的门,统筹调度整个子系统。
- **例子:** `VideoConverter.convert(file, "mp4")` 在背后默默调度编解码器、缓冲区和音频流。
- **代价:** 复杂度并没有消失——只是被藏了起来。精细控制仍然要绕过外观去做。
- **何时使用:** 大多数调用方只需要一两种简单场景。
- **关联:** **Adapter** 改变一个东西的接口;**Facade** 简化整个子系统。

### Proxy——控制访问的替身

> 名人的助理——你不能直接找到明星;助理负责筛选、安排日程,有时还替他们回答。

**Proxy(代理)**站在对象前面控制对它的访问——用于**懒加载(lazy loading)**、权限、缓存,或与远程的东西通信。同样的接口,前面多了一道门。

_Diagram: 客户端把代理当作真实对象来对话;代理决定何时——以及是否——把调用转发出去。_

#### Proxy (代理, stand-in)

- **问题:** 对象加载昂贵、需要权限,或者位于另一台机器上。
- **思路:** 一个接口相同的替身,站在真实对象前面。
- **例子:** 一个图片代理,在真正展示之前不加载真实文件;一个访问代理,在转发调用之前先检查权限。
- **代价:** 每次调用多一跳;延迟对调用方是隐藏的。
- **何时使用:** 懒加载、权限、缓存、远程对象。
- **关联:** 和 **Decorator** 形状相同,目的不同——访问控制。

### Composite——把一棵树和一片叶子同等对待

> 一个文件夹里有文件和其他文件夹。「计算大小」无论问一个文件还是整棵树,做法都一样。

**Composite(组合)**让你统一对待单个对象(**叶子**)和一组对象(**组合体**)——它们共用一个接口。**它把递归结构变成了你无需在每一层都做特殊处理就能操作的东西。**

_Diagram: 文件夹和文件响应同样的调用——遍历这棵树不需要特殊情况。_

#### Composite (组合, 树)

- **问题:** 处理一棵树,每一层都需要一个特殊情况。
- **思路:** 叶子和分支共用一个接口;分支只是去调用它的子节点。
- **例子:** 一个 UI,按钮和装满按钮的面板都能响应 `render()` 和 `getSize()`。
- **代价:** 共用的接口可能过于笼统:并非每个操作对叶子都有意义。
- **何时使用:** 数据天然就是一棵树:文件夹、UI、文档。
- **关联:** 用 **Iterator** 遍历;用 **Visitor** 添加新操作。

> **结构型的问题:** 「我是在改变对象做什么——还是只是在改变它被包装和被访问的方式?」

> 组合优于继承:在运行时把行为拼装起来,而不是把它冻结在一棵类继承树里。

## 4. 对话与分工

行为型模式的上半部分:对象如何分担工作、彼此对话——让责任划分干净,让正确的代码在正确的时刻运行。

### Strategy——换掉算法,调用方不变

> 一个地图应用:同一段行程,你选「最快」「最短」或「避开收费路段」,它就规划出不同的路线。你选策略;应用只是照着走。

**Strategy(策略)**让算法在运行时可以互换,调用方保持不变而方法可以变化。在现代语言里,这往往**只是传入一个函数**,而不是一整个类——这正是重点:模式是那个思想,不是那套仪式。

_Diagram: 结账流程调用一个 PricingStrategy;具体用哪种定价,在运行时决定。_

#### Strategy (策略)

- **问题:** 围绕同一算法的各种变体,`if` 阶梯越长越多。
- **思路:** 算法是接口背后一个可替换的对象(或函数)。
- **例子:** 一个接收 `PricingStrategy` 的结账流程:普通价、会员价或节日促销价。
- **代价:** 给一个永不变化的算法套上 Strategy 是累赘。
- **何时使用:** 有两个或更多变体,在运行时选择。
- **关联:** 和 **State** 形状相同。在现代代码里,常常只是一个函数。

### Observer——订阅,变了就通知你

> 一份新闻简报——你订阅一次,每一期新刊都会送到你手上。出版方不会给每位读者打电话;它只是发给名单。

在 **Observer(观察者)**中,**订阅者**向**主题**注册,主题一变化就通知它们——而主题并不知道它们是谁。**它是事件驱动和响应式代码的骨干。**

_Diagram: 单元格变了 → notify() → 每个依赖它的图表和表格自己更新。_

#### Observer (观察者, pub/sub)

- **问题:** 一个对象必须把变化告诉许多其他对象,却不能按名字认识它们。
- **思路:** 一份订阅者名单 + 每次变化时 `notify()`。
- **例子:** 电子表格的一个单元格刷新所有依赖它的图表;UI 框架对状态做出反应;到处都是 pub/sub。
- **代价:** 通知链很难追踪;被遗忘的订阅会永远存活。
- **何时使用:** 若干相互独立的部分要对同一个变化做出反应。
- **关联:** 事件驱动代码的基础——和 **Event-driven** 架构是一回事,只不过发生在一个程序内部。

### Command——把请求包装成对象

> 餐厅的点单——「5 号桌,两杯咖啡」这个请求变成了一张单子,你可以排队、重排、记录,甚至撕掉。

**Command(命令)**把一个动作变成一个独立的对象,于是你可以把它排队、记录、定时,或者**撤销**。**这是让一个动作变成可以存储、可以重放的东西的办法。**

_Diagram: 每次编辑都是一个带 do() 和 undo() 的对象;命令历史带来撤销/重做和宏。_

#### Command (命令)

- **问题:** 一个动作需要被延迟、重复、记录或撤销。
- **思路:** 把动作打包进一个对象,里面带着执行它所需的一切。
- **例子:** 编辑器里的每次编辑都是一个命令——因此有了撤销/重做和宏。作业队列就是等着轮到自己的命令。
- **代价:** 每个动作一个对象;每个命令的撤销都要单独想清楚。
- **何时使用:** 撤销/重做、队列、宏、动作日志。
- **关联:** 常与 **Memento** 搭配来实现撤销。

### Chain of Responsibility——一路传下去,直到有人处理

> 客服的分级——前台先试,升级给专家,再升级给经理,直到有人真能解决。

在 **Chain of Responsibility(责任链)**里,请求沿着一条**处理者**链传递;每个处理者要么处理它,要么传给下一个。**它让发送者与最终干活的人解耦。**

_Diagram: Web 中间件:认证 → 日志 → 限流 → 路由。任何一环都可以拦下请求。_

#### Chain of Responsibility (责任链, middleware)

- **问题:** 发送者不应该知道究竟是谁来处理请求。
- **思路:** 一条处理者链:每个做自己那一份,或者传下去。
- **例子:** Web 中间件:先认证,再日志,再限流,最后才是路由本身。
- **代价:** 各环的顺序很重要;请求可能走完整条链也没人处理。
- **何时使用:** 在主要工作之前,有若干相互独立的检查或步骤。
- **关联:** 在 Web 上你几乎总是以 **middleware** 的形式遇到它。

> **协作的问题:** 「我能不能改变由谁处理这件事、或怎么处理,而不让调用方察觉?」

> 好的行为型模式让两个部分能够合作,而不让任何一方把另一方写死在自己里面。

## 5. 状态、步骤、遍历

行为型模式的下半部分:对象的行为如何随状态变化,你如何逐个遍历集合,以及如何固定一副骨架、同时留出一个步骤开放。

### State——行为随所处的模式而变

> 红绿灯——绿、黄、红——每种颜色决定此刻发生什么,以及接下来是哪种颜色。同一盏灯,每种状态下行为不同。

**State(状态)**模式让对象在内部状态变化时改变自己的行为。一团纠缠的 `if` 和 `switch` 变成清晰的**状态**与**转换**——一台小小的**状态机**。**它把一堆乱糟糟的标志位变成一台整洁的小机器。**

_Diagram: 一份订单:待支付 → 已支付 → 已发货;只有在待支付或已支付时才能取消。_

#### State (状态, state machine)

- **问题:** 行为取决于某个模式,代码里塞满了标志位和 `switch`。
- **思路:** 每个状态是自己的对象,带着自己的规则和转换。
- **例子:** 一份订单在 `pending`、`paid`、`shipped` 或 `cancelled` 下行为各不相同;每个状态只知道自己的规则以及自己能变成什么。
- **代价:** 许多小类;只有两个状态时,一个普通的 `if` 就够了。
- **何时使用:** 状态很多,而且状态之间的转换很重要。
- **关联:** 和 **Strategy** 形状相同,但这里是对象在存活过程中自己切换状态。

### Iterator——逐个走过集合,而不看它的内部

> 电视遥控器的上一个和下一个——你在频道间切换,却不知道它们是怎么存放的。

**Iterator(迭代器)**提供一种统一的方式来遍历集合,而不暴露它是怎么构成的——数组、树或链表。**这个模式太有用了,语言通常已经内置了它**——`for..of`、生成器、`__iter__`。这是模式可以「毕业」成语言特性的最清楚的标志。

_Diagram: 数组、树和链表都给出同样的 next()——循环不知道里面是什么。_

#### Iterator (迭代器)

- **问题:** 遍历代码依赖于集合是怎么构成的。
- **思路:** 无论里面是什么,都用同一种方式取「下一个元素」。
- **例子:** `for (item of collection)` 对列表或自定义结构的工作方式都一样。
- **代价:** 接近于零——通常语言里已经内置了。
- **何时使用:** 总是——但要通过内置工具,而不是手写。
- **关联:** 遍历用 **Composite** 建起来的树。

### Template Method——固定的食谱,留一个空白待填

> 一份每次都一样的食谱——除了第四步「按你的口味加香料」。骨架是固定的;有一步归你。

**Template Method(模板方法)**在基类里定义一项操作的**骨架**,并允许重写其中特定的步骤,而不改变整体形状。当许多流程形状相同、只在一两处不同时很有用。

_Diagram: 基础导入器依次打开、读取、解析、保存;子类只提供解析这一步。_

#### Template Method (模板方法)

- **问题:** 几个形状相同的流程几乎完全互相复制。
- **思路:** 骨架放在基类里;会变化的步骤放在子类里。
- **例子:** 一个依次打开、读取、**解析(这一步由你填)**、保存的导入器;CSV 和 JSON 导入器只提供解析这一步。
- **代价:** 它依赖继承;骨架日后很难改。
- **何时使用:** 形状是共享的,差异只在一两处。
- **关联:** **Strategy** 用组合而不是继承来解决类似的问题。

### Mediator、Visitor、Memento——其余的,一口气讲完

> 工具箱里有些工具你每月才用一次,而不是每天——但你仍然应该知道它们存在。

还有几个模式偶尔会派上用场。**Mediator**——一座控制塔,让对象通过一个枢纽对话,而不是一团直接的连线。**Visitor**——在不修改结构中各个类的前提下,为它增加新操作。**Memento**——捕获对象的状态,以便日后恢复。认得这些名字;只有当确切的问题出现时才去用它们。

_Diagram: Mediator:一切经过枢纽。Visitor:新操作从外部遍历各个类。Memento:快照与恢复。_

#### Mediator (中介者)

- **是什么:** 一个枢纽,对象通过它对话,而不是彼此直接连接。
- **画面:** 一座控制塔:飞机和它对话,而不是彼此对话。
- **何时使用:** 对象之间的直接连接已经多得过头。

#### Visitor (访问者)

- **是什么:** 把对一个结构的新操作,挪到它自己的对象里。
- **例子:** 导出文档里的每个图形——而不修改图形类。
- **何时使用:** 结构是稳定的,但针对它的操作一直在增加。

#### Memento (备忘录, snapshot)

- **是什么:** 对象的一个已保存状态,你可以回到它。
- **例子:** 游戏里的存档点,编辑器里的撤销快照。
- **关联:** 和 **Command** 一起,就有了撤销。

> **控制的问题:** 「这一大片 `if` 森林,是不是其实是个 State 状态机或者 Strategy?」

> 当一个模式变成了语言关键字,就别再手写这个模式,直接用关键字。

## 6. 当代的看法

这份目录凝结了数十年来之不易的智慧——同时也是它所处时代的产物。像资深工程师那样使用它,而不是当作检查清单。

### Language features——许多模式如今已经内置

> 过去我们靠地标指路;现在手机来导航。这门技能并没有消失——它被工具吸收了。

Iterator 就是 `for..of`。Strategy 常常是传进来的一个函数。Decorator 和 Observer 在现代框架里是一等公民。当语言白送你那个形状时,**手写完整的模式就只是仪式(样板代码,boilerplate)**。思想仍然重要;样板代码通常不重要。

_Diagram: 左边是目录里的模式;右边是语言或框架已经替你提供的东西。_

### Pattern-itis——一种真实的病

> 一个把整个调料架都倒进一道菜里的厨子,不会得到更丰富的一餐——只会得到一盘没法吃的东西。

**Pattern-itis(模式病)**是典型的**过度设计(over-engineering)**坏味道:为了显得高明而去拿模式。而现在更糟——**AI 助手会很乐意为本该三行搞定的事生成一个「多层 Abstract Factory」**。更简单的代码几乎总是更资深的选择。

#### Pattern-itis (模式病, over-engineering)

- **是什么:** 为模式而模式,而不是为了缓解某种疼痛。
- **症状:** 为一个产品建的抽象工厂,为一个永不变化的算法建的 Strategy,围绕一个 `if` 的五层间接。
- **为什么现在更糟:** AI 助手生成这些结构既积极又快。
- **解药:** 先写最简单的代码;疼痛出现了再用模式。

### 模式是词汇,不是法律

> 认识很多词并不会让人成为好作家。知道该用哪个词——以及何时保持沉默——才会。

这份目录真正的价值是**共享的语言**:「这里放个 Facade 吧」,对任何懂这个术语的人都是一句话就明白,这在团队里价值很大。但目标始终是能解决问题的最简单的代码。模式是手段;说出一个模式的名字从来不是成就。

> **当代的检验:** 「更简单的代码——一个函数、一个普通对象、一个语言特性——能不能同样好地做到这件事?」

> 学会所有模式,是为了能认出它们。能少用就少用。

## 7. 选得好

模式是答案。本事在于把它们对上一个真实的问题——并知道什么时候不用任何模式才是正确答案。

### 让模式从疼痛中长出来

> 你不会在找到螺栓之前就挑好扳手。先看到问题,再去拿合适的工具。

好的模式用法通常是一次**重构**,而不是一开始的蓝图:你先写简单的东西,感到某种具体的疼——这个 `if` 阶梯一直在变长,这个类对自己是怎么构造的知道得太多——**然后**缓解它的那个模式就显而易见了。一上来就硬套模式,就是在盖没人要的大教堂。

### 只在名字有帮助时才起名

> 闲聊时你不会说「我执行了一次撞墙配合」。但在球场上,和队友在一起,这个词能省下一整句话。

模式的回报是沟通和灵活性。如果起个名字能让下一个人更容易看懂代码,就大声用这个名字——放进类名、注释、设计讨论里。如果间接层什么也没换来,那就最朴素的代码获胜,不管它是不是模式。

### 模式之间是相互关联的

> 一门语言里的词不是孤立存在的:它们有近义词、有搭档、有亲戚。

Adapter、Decorator 和 Proxy 是**同一种形状**:包在对象外面的一层,只是目的不同。Strategy 和 State **形状相似**。Command 和 Memento 合在一起给出**撤销**。Abstract Factory **由** Factory **构成**。Composite 用 **Iterator** 遍历。Observer 是事件驱动代码的**骨干**。

_Diagram: 本课各模式之间的主要关联。_

#### 在伸手去拿模式之前

- **它解决的是什么反复出现的问题?** 用一句话说出来。
- **我现在就有这个问题吗**,还是在猜测未来?
- **一个普通函数或对象够用吗?** 先试试那个。
- **我的语言是不是已经白送了这个?**
- **给它起名,会让下一个人更容易看懂代码吗**——还是只是更花哨?
- **我是在朝它重构**,还是一上来就硬塞进去?

#### 判断你是否过度设计的气味测试

- 你有一个**只构造一样东西的工厂**。
- 一个**只有一种策略的 Strategy**,而且永远不变。
- **接口和间接层比实际行为还多**。
- 你加这个模式是**为了显得资深**,不是为了解决疼痛。
- 新读者需要一张 **UML 图**才能看懂三行逻辑。

#### 你选得好的迹象

- 这个模式**去掉了一条**不断变长的 **`if`/`switch` 阶梯**。
- 你可以**替换一个实现**而不用动调用方。
- 同事一听名字就**认出了那个形状**。
- 间接层**物有所值**——你确实在用它带来的灵活性。
- 有这个模式的代码**比没有它时更简单**,而不只是更聪明。

## 速查表:全部 20 个模式与关键思想

### 基础概念

- **Design Pattern** (设计模式): 对代码中反复出现的问题的、有名字的解法。是词汇,不是库。
- **Gang of Four** (GoF): 《Design Patterns》的作者们,他们编目了 23 个模式。
- **Indirection** (间接层): 你和那个东西之间的一层。每个模式的代价。
- **Pattern-itis** (模式病, over-engineering): 为了展示而不是为了解决疼痛的模式。简单的代码几乎总是获胜。

### 创建型

- **Factory** (Factory Method, 工厂): 按名字要一个对象;由工厂选择类。
- **Abstract Factory** (抽象工厂): 为一整族相配的对象准备的工厂。
- **Builder** (建造者): 一步一步造出复杂对象,最后 `build()`。
- **Singleton** (单例): 人人共用恰好一个实例。小心:全局状态。
- **Prototype** (原型): 克隆一个现成的对象再稍作调整。

### 结构型

- **Adapter** (适配器): 一个转换器:让一个接口看起来像另一个。
- **Decorator** (装饰器): 包装层添加行为;接口保持不变。
- **Facade** (外观): 通向复杂子系统的一扇简单的门。
- **Proxy** (代理): 站在对象前面,控制访问。
- **Composite** (组合): 叶子和树共用一个接口。

### 行为型:对话

- **Strategy** (策略): 可替换的算法;常常只是一个函数。
- **Observer** (观察者): 订阅者会被告知变化。
- **Command** (命令): 把动作变成对象:排队、记录、撤销。
- **Chain of Responsibility** (责任链, middleware): 处理者排成一条链;在 Web 上就是中间件。

### 行为型:状态与步骤

- **State** (状态): 每个状态有自己的规则和转换。
- **Iterator** (迭代器): 不看内部就能遍历集合。通常就是 `for..of`。
- **Template Method** (模板方法): 骨架是固定的;有一步归你。
- **Mediator** (中介者): 所有人都通过一个枢纽对话。
- **Visitor** (访问者): 不修改各个类就能加一个新操作。
- **Memento** (备忘录): 保存一个状态,日后回到它。

> 设计模式最好的用法,是读者从未察觉的那一种——因为它让代码显得理所当然。

速成课结束 · 7 章 · 20 个模式

接下来是源头:Gang of Four 的 _Design Patterns_,以及它那些更友善的当代同伴——_Head First Design Patterns_、refactoring.guru、Martin Fowler 的 _Refactoring_。但要像你学棋开局那样去读它们:不是为了背招法,而是为了当那个局面出现在你自己的棋盘上时,能认出它。
