# 经典设计模式浅析

> 用白话把 GoF 23 种设计模式讲清楚。

- 作者：David（道雾轩）
- 最后更新：2026-08-03
- 原文：https://daiw.org/manual/design-patterns
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

先说一句可能得罪人的大实话：**大部分人是先会背 23 个模式的名字，再慢慢忘光，最后在某次重构里自己重新发明了其中几个——然后才真正懂了它们。**

这个系列想把顺序倒过来：先让你看见痛，再告诉你前人是怎么止痛的。

## 设计模式到底是个啥

把话说白了：**设计模式就是前人在某个坑里摔多了，爬出来之后在坑边立的一块牌子，上面写着“这么走能绕过去”。**

它不是语法，不是框架，不是你 `import` 进来就能用的库。它是一种**在特定场景下反复被验证有效的对象组织套路**。1994 年，四位作者(业内昵称 GoF,Gang of Four,“四人帮”)把当时面向对象界反复出现的 23 种套路收集成册，写了本《Design Patterns》，从此这 23 个就成了程序员的“普通话”——你说“这里用个观察者”，对面立刻懂，不用画半小时图。

<Callout type="info">
  所以模式最大的价值有两层：**一层是解决方案本身**(怎么写)，**另一层是词汇表**(怎么沟通)。后者常被低估——团队里能用一个词对齐一整套设计，是极高效的事。
</Callout>

## 为什么会有这些痛点：一切都因为“变”

如果需求永远不变，世界上就不需要设计模式——一把梭把功能写完，能跑就行。

问题是需求**一定**会变。今天只支持支付宝，明天加微信，后天老板要加数字货币；今天导出 PDF，下周要导出 Excel。**几乎所有设计模式，本质上都在回答同一个问题：当某个东西要变的时候，怎么让改动的影响范围最小？**

GoF 有一句纲领性的话，值得刻在脑子里：

> **Encapsulate the concept that varies.**(把“会变的那部分”封装隔离起来。)

你会发现，23 个模式几乎都是这句话在不同场景下的变体：策略模式隔离“会变的算法”，观察者模式隔离“会变的通知对象”，工厂模式隔离“会变的具体类”……**看模式时别只盯着类图，要问一句：它到底在隔离什么变化？** 抓住这条线，23 个模式就不再是 23 条孤立的口诀，而是一张网。

## 三大类，一句话分清

GoF 把 23 个模式按“管什么”分成三类，这个分法非常好记：

| 类别 | 管什么 | 一句话 | 典型代表 |
| --- | --- | --- | --- |
| **创建型** | 对象**怎么生** | 把 `new` 这件事管起来，别到处乱 new | 单例、工厂、建造者 |
| **结构型** | 对象**怎么拼** | 把小对象组装成大结构，还不别扭 | 适配器、装饰器、代理 |
| **行为型** | 对象**怎么处** | 对象之间怎么分工、怎么通信 | 策略、观察者、命令 |

创建型管“生”，结构型管“拼”，行为型管“处对象关系”——**生、拼、处**，三个字记一辈子。

## 每篇怎么讲

为了不写成第 101 本“设计模式速查手册”，本系列每个模式都按同一条线索展开，力求把“背名词”变成“懂痛点”：

1. **这是什么** —— 先用一个现实生活里的例子把直觉建立起来(遥控器、咖啡加料、公司审批流……)，再回到代码。
2. **解决什么痛** —— 不先讲解法，先把“不用这个模式会有多难受”演示给你看。痛点不清，解法无感。
3. **怎么实现** —— 给出 **Go** 的惯用实现(必要时对比 **C++ / Java**)，不堆代码，只讲关键结构。
4. **有哪些坑** —— 模式用错比不用还糟。这一节专讲过度设计、误用、以及“看起来像其实不是”的陷阱。
5. **开源里怎么用** —— 到真实的知名项目(Go 标准库、Kubernetes、Java JDK、Nginx……)里，把这个模式活生生指给你看。

<Callout type="warn">
  一句提醒贯穿全系列：**模式是解药，不是维生素**。没病别乱吃。见到痛点才用对应的模式，是工程师；为了用模式而制造痛点，是“模式病”。本系列每一篇的“坑”小节，有一半篇幅在劝你别乱用。
</Callout>

## 关于代码

- **主语言 Go**：用 Go 的惯用法(小接口、组合、函数值、`error` 返回)来写，你会发现不少“经典 OOP 模式”在 Go 里轻得几乎看不见——这本身就很说明问题。
- **辅以 C++ / Java**：在能体现差异的地方，顺手对比一下 C++ 和 Java 的写法，看看同一个痛点在不同类型系统下怎么止。
- **完整可运行代码**都在配套开源仓库，9 种语言对照实现，建议边读边跑：
  👉 [github.com/davidapp/GoF](https://github.com/davidapp/GoF)

准备好了就从[单例模式](https://daiw.org/manual/design-patterns/singleton)开始——它最简单，却也是**争议最大、被骂最多**的一个，适合用来热身。

## 本专栏篇目

- [写在前面 · 模式是给痛点立的牌子](https://daiw.org/manual/design-patterns.md): 用白话把 GoF 23 种设计模式讲清楚。

### 创建型 · 对象从哪来

- [01 单例（创建型）](https://daiw.org/manual/design-patterns/singleton.md): 保证一个类型只有一个实例、并给一个全局访问点。听起来最简单，却是被骂得最惨的模式——本篇讲清它到底解决什么痛、Go 里的 sync.Once 惯用法、以及它为什么常被当成“披着模式外衣的全局变量”。
- [02 工厂方法（创建型）](https://daiw.org/manual/design-patterns/factory-method.md): 当“到底该 new 哪个具体类”本身就是会变的东西时，把这个决定从业务代码里赶出去，下放给专门的工厂。讲清工厂方法解决的耦合痛点、Go 的接口实现、以及它和“简单工厂”的区别。
- [03 抽象工厂（创建型）](https://daiw.org/manual/design-patterns/abstract-factory.md): 工厂方法一次造一个产品，抽象工厂一次造一整族配套产品——还保证它们不串味(不会 Windows 按钮配 macOS 复选框)。讲清“产品族”痛点、Go 实现，以及它对“加产品族友好、加产品种类痛苦”的著名短板。
- [04 建造者（创建型）](https://daiw.org/manual/design-patterns/builder.md): 当一个对象零件又多又杂、还一堆可选项时，别再写那种参数排到天边的构造函数了。讲清“可伸缩构造函数”的痛、建造者的分步组装 + 最终校验，以及 Go 里更地道的 functional options 替代。
- [05 原型（创建型）](https://daiw.org/manual/design-patterns/prototype.md): 从头造一个对象太贵？那就照着现成的拷一个。讲清原型模式的“以复制代替新建”思路、Go 结构体值拷贝的天然优势，以及深拷贝 vs 浅拷贝这个能坑死人的头号陷阱。

### 结构型 · 对象怎么拼

- [06 适配器（结构型）](https://daiw.org/manual/design-patterns/adapter.md): 手头有个现成的类/SDK，接口却和你系统期望的对不上，又不能改它——加个转换层。讲清适配器的“接口转换”本质、Go 里的对象适配、以及 http.HandlerFunc 这个把函数适配成接口的神来之笔。
- [07 装饰器（结构型）](https://daiw.org/manual/design-patterns/decorator.md): 想给对象动态叠功能，又不想被继承的组合爆炸淹死。讲清装饰器“同接口层层包裹”的思路、Go 的组合实现，以及为什么 Go 的 io 包是学装饰器的绝佳教材。
- [08 代理（结构型）](https://daiw.org/manual/design-patterns/proxy.md): 在访问一个对象前后插一脚——懒加载、权限、缓存、远程调用——却不改这个对象、也不让调用方察觉。讲清代理与装饰器这对“双胞胎”的本质区别，以及虚拟/保护/远程/缓存四种代理。
- [09 外观（结构型）](https://daiw.org/manual/design-patterns/facade.md): 一个子系统里一堆类、一长串调用顺序，客户端用得头大。给它配一个“一键搞定”的高层入口。讲清外观的化繁为简、它和适配器的区别，以及为什么 http.Get() 就是个外观。
- [10 桥接（结构型）](https://daiw.org/manual/design-patterns/bridge.md): 一个东西有两个独立变化的维度，用继承会让类的数量乘起来爆炸。桥接把两个维度拆成两条独立的类层次，用组合搭一座桥，让 M×N 变回 M+N。附 database/sql 就是一座桥的实锤。
- [11 组合（结构型）](https://daiw.org/manual/design-patterns/composite.md): 对象能包含同类对象、层层嵌套成一棵树(文件夹装文件也装文件夹)，而你想“一视同仁”地对待单个和一组。讲清组合模式的递归结构、透明性与安全性的经典取舍，以及 go/ast 这样的真实树。
- [12 享元（结构型）](https://daiw.org/manual/design-patterns/flyweight.md): 系统里有海量对象，大部分状态还都重复，内存扛不住。把“可共享的内在状态”抽出来复用，只给每个实例留“随它变化的外在状态”。讲清内在/外在状态的划分，以及 Java Integer 缓存这个经典享元池。

### 行为型 · 对象怎么处

- [13 策略（行为型）](https://daiw.org/manual/design-patterns/strategy.md): 一件事有多种可互换的做法，别再用 if-else 把它们堆在一个函数里。策略模式把每种算法封成可替换的一块，运行时随意切换。Go 里一个函数类型就搞定，几乎看不见“模式”的影子。
- [14 观察者（行为型）](https://daiw.org/manual/design-patterns/observer.md): 一个对象状态变了，一大群依赖它的对象要自动跟着更新——还不能让它俩硬耦合。观察者是“事件驱动”整个世界的地基。讲清一对多通知、推拉模型、忘记退订的内存泄漏，以及 Go channel 这个更 Go 的答案。
- [15 命令（行为型）](https://daiw.org/manual/design-patterns/command.md): 把“一个操作请求”本身打包成对象，于是它能被存储、排队、记录、撤销、重做。讲清命令模式怎么把“转瞬即逝的方法调用”变成“能攥在手里的东西”，以及 undo/redo、任务队列背后的它。
- [16 模板方法（行为型）](https://daiw.org/manual/design-patterns/template-method.md): 一堆流程骨架相同、只有个别步骤不同——把骨架写一次，把变化的步骤留给各自填。讲清“好莱坞原则”(别调用我，我会调用你)、模板方法与策略的区别，以及 Go 用组合+接口如何表达这个本靠继承的模式。
- [17 迭代器（行为型）](https://daiw.org/manual/design-patterns/iterator.md): 顺序访问一个集合的元素，却不用关心它内部是数组、链表还是树。讲清迭代器怎么把“遍历”从“集合结构”里解耦出来，以及 Go 从 for-range 到 1.23 range-over-func 把这个古老模式内建进语言的过程。
- [18 状态（行为型）](https://daiw.org/manual/design-patterns/state.md): 一个对象的行为随内部状态而变——同一个按钮在“播放中”和“已停止”时干的事完全不同。讲清状态模式怎么消灭满屏的 if(state==...)，以及它和策略模式那个“结构一样、灵魂不同”的经典辨析。
- [19 责任链（行为型）](https://daiw.org/manual/design-patterns/chain-of-responsibility.md): 一个请求可能由多个处理者之一负责，而发送者不该知道到底该谁处理。把处理者串成一条链，请求顺着链走直到被处理。讲清这个模式，以及为什么所有 Web 中间件本质上都是它。
- [20 中介者（行为型）](https://daiw.org/manual/design-patterns/mediator.md): 一堆对象两两互相引用，乱成一张 N×N 的网，牵一发动全身。引入一个“中间人”把网状关系收拢成星状，大家只跟中间人打交道。讲清中介者的解耦、它最大的坑(上帝对象)，以及和观察者/外观的区别。
- [21 备忘录（行为型）](https://daiw.org/manual/design-patterns/memento.md): 保存一个对象某时刻的完整状态，以便之后一键恢复(撤销/读档)，还不破坏它的封装。讲清备忘录的三个角色、“管理者只保管不偷看”的封装保护，以及它和命令模式在实现 undo 时的分工。
- [22 访问者（行为型）](https://daiw.org/manual/design-patterns/visitor.md): 元素类型稳定，却要频繁给它们新增各种操作(算面积、渲染、导出...)——把“操作”从元素类里抽出去，新增操作只需加一个访问者。讲清双重分派这个绕人的核心机制，以及访问者“加操作易、加类型难”的著名短板。
- [23 解释器（行为型）](https://daiw.org/manual/design-patterns/interpreter.md): 给一个简单的小语法(算术表达式、查询条件、规则)定义规则并求值，把句子表示成一棵语法树递归解释。讲清它的原理，以及为什么它是 23 个模式里最冷门、现实中你几乎永远不该手写的那一个。
