讲解
装修房子时,没有师傅会从「水管应该怎么拐弯」开始重新发明——水电布线、防水处理、吊顶龙骨,都有行业几十年沉淀下来的成熟做法,师傅们张口就是行话,一说大家就懂。编程也一样:对象之间怎么协作、变化怎么隔离、扩展怎么预留,这些问题在无数项目里被反复遇到,也早就有了一批被反复验证过的解法。设计模式就是这些解法被命名、被整理之后的样子。
设计模式不是能直接抄的代码,而是一种「命名的经验」。它描述的是:在某种反复出现的场景下,对象和类应该怎样组织。1994 年,四位作者(Gamma、Helm、Johnson、Vlissides,人称 GoF——Gang of Four)在《设计模式:可复用面向对象软件的基础》里系统整理了 23 种模式,这本书至今仍是这个领域的坐标原点。
GoF 把 23 种模式分成三大类。创建型模式(工厂方法、抽象工厂、单例、建造者、原型)关注「对象怎么 new 出来」,把创建逻辑从使用逻辑里解耦;结构型模式(适配器、桥接、组合、装饰器、外观、享元、代理)关注「类和对象怎么拼装」,用组合而非继承搭出灵活的结构;行为型模式(责任链、命令、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者,外加解释器)关注「对象之间怎么沟通、职责怎么分配」。
学设计模式最大的价值不是背下 23 个名字,而是获得一套 vocabulary:评审时说「这里用观察者比直接回调好」,一句话就传递了整个结构。同时也得泼一盆冷水:模式是工具不是目标,为模式而模式是初学者最常见的弯路,本教程最后一章会专门讨论「什么时候不该用模式」。
本教程用 TypeScript 实现全部模式——接口、抽象类、私有字段让这些模式的「契约」表达得比任何语言都清楚。每一章的示例都用 tsc 严格模式编译并真实运行过(断言通过才允许发布),建议你亲手敲一遍,再试着把示例改成自己项目里的场景。
示例
先热个身,感受一下「面向接口编程」——几乎所有模式的基石。下面定义一个通知接口和两个实现,调用方只认识接口,不认识具体类:
import assert from 'node:assert/strict';
// 调用方只依赖这个抽象
interface Notifier {
send(message: string): string;
}
class EmailNotifier implements Notifier {
send(message: string): string {
return '邮件发送:' + message;
}
}
class SmsNotifier implements Notifier {
send(message: string): string {
return '短信发送:' + message;
}
}
// notifyAll 不知道、也不需要知道具体是哪种通知方式
function notifyAll(notifiers: Notifier[], message: string): string[] {
return notifiers.map((n) => n.send(message));
}
const results = notifyAll([new EmailNotifier(), new SmsNotifier()], '服务器告警');
assert.deepEqual(results, ['邮件发送:服务器告警', '短信发送:服务器告警']);
console.log(results.join(';'));
运行输出:
邮件发送:服务器告警;短信发送:服务器告警
这段代码里已经藏着后面要正式登场的思想:notifyAll 依赖抽象(Notifier 接口)而非具体实现,新增一种通知方式(比如 WebhookNotifier)不需要改 notifyAll 一行代码。记住这个感觉,它是后面二十多章反复出现的主题。
常见坑
- 把设计模式当代码模板背:模式是「问题 + 场景 + 解法」三件套,跳过问题直接套解法,十有八九是过度设计。
- 以为学了模式代码就一定更好:模式换来的是灵活性,代价是多出来的类和间接层;简单场景直接用直白代码反而是最佳实践。
- 纠结语言的模式实现差异:GoF 原书用 C++/Smalltalk 举例,有些模式(如迭代器、策略)在现代语言里已被语言特性吸收,学思想不学形。
- 一上来就啃访问者、解释器:模式有难易之分,按本教程顺序从工厂、单例、观察者这些高频模式入手,循序渐进。
- 以为模式只对大型项目有用:两人协作的小项目里,一句「这里是个策略」省下的沟通成本同样真实。
小结
设计模式是被命名、被验证过的对象组织经验,GoF 把它们分成创建型、结构型、行为型三类共 23 种;学习的重点是「什么场景用什么模式」,而不是背诵类图。下一章先建立评判所有模式的标尺——SOLID 原则。