设计模式:范式的应用
1994 年,GoF 做了一件重要的事:不是发明解决方案,而是给已经存在的解决方案命名。
Observer、Strategy、Factory——这些解法在 GoF 之前就被无数程序员独立发现过。GoF 的贡献不是创造,而是命名。
命名本身就是力量。有了”Observer”这个词,你一句话就能对齐整个团队的设计意图;没有这个词,你要画图、解释结构、说明动机——每次都从零开始。模式的第一个价值是词汇,不是代码模板。
但 Peter Norvig 在 1996 年指出:GoF 的 23 个模式中,有 16 个在动态语言里”透明”——不是消失了,而是语言已经把它表达的能力内建了。Strategy 在 Python 里退化为传一个函数,Iterator 在现代语言里退化为 for...of。
这引出一个更深的问题:如果模式在更强大的语言里消失,它们究竟是什么?
一、模式是什么
不是模板,是被命名的结构
模式不是要背诵的 23 个条目,不是可以粘贴的代码模板。它是一个被命名的、反复出现的结构性解决方案。
三个核心要素:
- 问题:一个反复出现的设计场景(“需要在不修改算法调用方的前提下替换算法”)
- 解法:一种结构关系(“把算法抽象成接口,运行时注入具体实现”)
- 名字:让解法可以被识别和传递(“Strategy 模式”)
三者缺一不可,但名字是核心。没有名字的解法存在于每个人的经验里,无法流通;有了名字,它成为共享词汇,跨团队、跨项目传播,成为工程师之间的暗语。
模式是范式的词汇表
每个范式成熟之后,都沉淀出一套词汇——用来表达这个范式内的常见结构,捕捉这个范式下反复出现的问题和解法:
| 范式 | 核心词汇 |
|---|---|
| OOP | Factory、Singleton、Observer、Strategy、Decorator、Composite |
| 函数式 | Functor、Monad、Lens、Fold、Free Monad |
| 响应式 | Observable、Signal、Operator、Backpressure、Subject |
| 过程式 | 状态机、责任链、命令队列、协程 |
| 泛型 | Typeclass、Trait bound、Zero-cost abstraction、GAT |
GoF 的 23 个模式是 OOP 的词汇表——它们出现是因为 OOP 的封装和继承在工程实践中产生了反复出现的结构性问题,这些问题被识别、被命名、被系统化。
理解一个范式,很大程度上就是学习它的词汇表。词汇表越丰富,说明这个范式越成熟,处理过的问题越多。
二、命名与压缩:模式的认知价值
命名即压缩
专家之间的沟通为什么高效?因为他们共享词汇。
“这里用 Strategy”,七个字,传达了:把算法抽象成接口、运行时注入、调用方不感知具体实现的完整结构意图。没有这个词,你需要画图、解释、举例。
模式名是有损压缩——丢掉了实现细节,保留了设计意图。压缩率越高,沟通效率越高。这就是为什么 GoF 即使在 30 年后仍然被引用:它提供了一套高效的设计语言,让程序员能在短时间内对齐复杂的结构决策。
代价是误解的风险——用同一个词,不同人可能理解的是不同变体。“你说的 Factory 是 Factory Method 还是 Abstract Factory?“这是词汇共享的暗面:名字压缩了信息,也压缩掉了精确度。
模式密度:语言表达力的反指标
一个反直觉的认知工具:代码库里需要手动实现的模式越多,语言的表达力就越弱。
Java 实现 Strategy 模式:
interface SortStrategy {
void sort(int[] data);
}
class BubbleSort implements SortStrategy {
public void sort(int[] data) { ... }
}
class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) { this.strategy = strategy; }
public void sort(int[] data) { strategy.sort(data); }
}Python 实现同样意图:
def sort_with(data, strategy):
strategy(data)
sort_with(data, bubble_sort)Strategy 模式没有消失,它表达的意图(“把算法作为参数传入”)仍然存在。消失的是实现它所需的样板代码——因为 Python 的一等函数直接提供了这个能力。
这就是模式密度作为反指标的含义:看一个语言需要多少显式模式,就能判断它在哪些维度上缺乏表达力。Java 高密度,Haskell 低密度,不是因为 Java 程序员不懂设计,而是因为 Haskell 已经把更多的设计意图内建为语言特性。
三、从模式到语言特性的演化弧
模式是语言特性等待被发明的化石
设计模式有一条清晰的演化弧线:
问题反复出现
↓
程序员独立发现解法
↓
解法被命名为模式(GoF 阶段)
↓
模式被广泛采用
↓
下一代语言把它内建为语法或标准库
↓
模式"消失"——能力留下,名字不再需要这不是猜测,是已经发生的历史:
| 模式 | 演化轨迹 | 当前状态 |
|---|---|---|
| Iterator | GoF(1994)→ for...of / .iter() | 所有现代语言已内建,模式消失 |
| Singleton | GoF(1994)→ 模块系统 | import 天然单例,双重检查锁成为历史 |
| Strategy | GoF(1994)→ 高阶函数 | FP 语言中完全透明 |
| Decorator | GoF(1994)→ @decorator 语法 | Python / TypeScript 已成语言内建 |
| Observer | GoF(1994)→ EventEmitter → Signal | Signal 正在成为语言级原语(TC39 提案) |
| Monad | Haskell 理论 → async/await | 异步 Monad 已被所有现代语言内建 |
最后一个值得展开:async/await 是 Monad 的工业化落地。
# async/await 的本质:把"可能异步"编码进类型
async def fetch_user(id: int) -> User:
data = await http.get(f"/users/{id}")
return User.from_dict(data)await 就是 Monad 的 bind(>>=)——把一个带上下文的值(Coroutine)展开,取出内部值,继续计算。大多数人用 async/await 但不知道它叫 Monad,更不会去看 Haskell 的类型论。这正是”模式内建后变透明”的完美例证:能力被所有人使用,名字只有专家才知道。
消解不是消亡
模式消解后,它解决的问题并没有消失,只是解决方式变得更自然:
// Rust:Iterator 模式完全内建
// 没有 hasNext() / next(),直接用迭代器链
let result: Vec<i32> = numbers.iter()
.filter(|&&x| x > 0)
.map(|&x| x * 2)
.collect();# Python:Singleton 不需要模式
# 模块在进程内只被加载一次——天然单例
# config.py
DATABASE_URL = "postgresql://..."
# main.py
from config import DATABASE_URL # 每次 import 拿到同一个对象消解的方向揭示了语言的哲学立场:一门语言选择内建哪些模式,就是在宣布它认为哪些设计意图足够基础、足够普遍,值得成为语言的一部分。
Python 内建了 Iterator 和 Decorator,说明它认为”遍历抽象”和”动态增强”是基础能力。Rust 内建了 trait 和 enum + match,说明它认为”行为约定”和”穷尽类型”是基础能力。
四、GoF 三类与范式的对应
GoF 把 23 个模式分为三类,每类的出现都有其范式根源。
创建型:OOP 封装的代价
创建型模式(Factory、Builder、Singleton、Prototype)解决”对象如何被创建”。
这类模式的出现是因为 OOP 的封装把构造逻辑变复杂了。当一个对象的创建依赖于配置、依赖于运行时条件、依赖于产品族的一致性时,直接调用构造函数不够用——需要专门的词汇来管理创建过程。
# Factory Method:创建与使用解耦
class NotificationFactory:
@staticmethod
def create(channel: str) -> Notification:
match channel:
case "email": return EmailNotification()
case "sms": return SMSNotification()
case "push": return PushNotification()函数式语言几乎不需要创建型模式——因为值是不可变的,“创建”就是调用函数,没有复杂的初始化过程需要管理。
结构型:组合论的工程化
结构型模式(Adapter、Decorator、Proxy、Composite、Bridge)解决”对象如何连接”——直接对应 01 中 OOP 组合论的工程实践。
Decorator 模式是组合优于继承的经典应用:
# 继承:紧耦合,修改父类行为
class LoggingList(list):
def append(self, item):
print(f"Adding: {item}")
super().append(item)
# Decorator:组合,动态添加行为
class LoggingDecorator:
def __init__(self, wrapped):
self._wrapped = wrapped
def append(self, item):
print(f"Adding: {item}")
self._wrapped.append(item)Adapter 在结构化类型的语言里自然消解:TypeScript 的”形状即身份”让接口转换不需要显式适配器;Go 的隐式接口实现让类型只要有正确方法就自动满足接口。
行为型:时态观的工程投影
行为型模式(Observer、Strategy、State、Command、Chain of Responsibility)解决”对象如何交互”——是 OOP 时态观的工程延伸:当状态变化时,谁负责通知,谁负责响应?
State 模式是最直接的时态工程化:把状态机的每个状态显式建模,状态转移封装在状态对象内部。
# State 模式:显式状态机
class TrafficLight:
def __init__(self): self.state = RedState()
def change(self): self.state = self.state.next()
class RedState:
def next(self): return GreenState()
class GreenState:
def next(self): return YellowState()
class YellowState:
def next(self): return RedState()Rust 把这个模式提升为语言特性:enum + match 自带穷尽检查,编译器保证你没有遗漏任何状态:
enum TrafficLight { Red, Yellow, Green }
fn next(light: TrafficLight) -> TrafficLight {
match light {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Green => TrafficLight::Yellow,
TrafficLight::Yellow => TrafficLight::Red,
}
// 漏掉任何一个分支 → 编译错误
}State 模式在 Rust 里不需要三个类,只需要一个 enum。模式消解了,但类型安全的状态机能力反而更强了。
反模式:词汇表的阴暗面
模式的词汇表有正面(识别好的结构),也有反面:反模式——命名常见的错误结构,让团队能识别并修复它。
| 反模式 | 它违背的设计原则 | 模式层面的解药 |
|---|---|---|
| God Class | SRP——单一职责 | 拆分为多个专职对象 |
| Callback Hell | 可读性与可维护性 | Promise → async/await(Monad 内建) |
| Golden Hammer | 适配性——工具匹配场景 | 理解多种范式,按场景选择 |
| Anemic Domain Model | OOP 封装——行为应在数据旁 | 领域对象承担自己的业务逻辑 |
| Premature Optimization | 认知成本与实际收益 | 先让代码正确,再让代码快 |
Callback Hell 是最好的反模式例子——它不只是代码风格问题,而是异步 Monad 缺失时的结构性必然。async/await 出现后,Callback Hell 从”需要靠纪律避免的反模式”变成了”语言不允许你写出来的结构”。反模式的消解和模式的消解同理:语言内建了正确的做法,错误的做法就自然变难写了。
五、多语对照:模式消解地图
三门语言在模式消解上的哲学选择:
| 模式 | Python | TypeScript | Rust |
|---|---|---|---|
| Strategy | 直接传函数(Callable) | 函数类型 / 接口 | impl Fn / trait 对象 |
| Observer | 回调列表 / asyncio | EventEmitter / Signal | channel(broadcast) |
| Iterator | 生成器 + __iter__ 协议 | Symbol.iterator / Generator | Iterator trait(零成本) |
| Decorator | @decorator 语法(语言内建) | TC39 Decorators | 组合 + trait 泛型包装 |
| Singleton | 模块天然单例 | const 模块导出 | static / OnceLock |
| Adapter | 鸭子类型自动适配 | 结构化类型自动满足 | trait 隐式实现 |
| State | enum(3.10+)+ match | Discriminated union | enum + 穷尽 match |
| Command | 闭包 / 可调用对象 | 闭包 / 函数类型 | 闭包 / Box<dyn Fn> |
消解最彻底的方向:
- Python:行为型模式因一等函数大量消解;模块系统消解了 Singleton;鸭子类型消解了 Adapter
- TypeScript:结构化类型消解了所有需要显式声明实现关系的模式;类型体操本身成为一套新的声明式词汇
- Rust:trait + enum + match 重新定义了行为型模式的表达方式;没有继承,模板方法随之消失;所有权系统隐式实现了 RAII(资源获取即初始化)
三门语言的哲学立场总结:
- Python:信任程序员,把能省的样板全省掉,鸭子类型是终极适配器
- TypeScript:类型系统本身是更强大的设计词汇,结构即契约
- Rust:编译器替代运行时检查,让模式的约束由类型系统而非纪律来保证
延伸阅读
- Gamma et al.《Design Patterns: Elements of Reusable Object-Oriented Software》(1994) — GoF 经典
- Norvig, Peter. “Design Patterns in Dynamic Languages.” (1996) — 模式消解的先驱论文
- Seemann, Mark. “Patterns as Signs of Language Deficiency.” — 模式密度作为语言表达力反指标
- Evans, Eric.《Domain-Driven Design》(2003) — 领域层面的词汇表建设
关联 meta 维度
- 01 面向对象编程 — GoF 模式的母范式;SOLID 是组合哲学的工程总结
- 02 函数式编程 — Monad 内建为 async/await;高阶函数消解行为型模式
- 05 响应式与事件驱动 — Observer 演化为 Signal 的完整轨迹
- meta: 类型系统 — 类型系统越强,需要的显式模式越少