启程·设计模式的魔法契约 / Departure · The Magical Contract of Design Patterns
启程·设计模式的魔法契约 / Departure · The Magical Contract of Design Patterns
一、阁楼上的雷雨夜 / A Stormy Night in the Attic
雷声轰隆隆地滚过天际,像一串拖长的代码错误提示音。
小码哥蜷缩在祖父老屋的阁楼里,手里攥着一支发着微弱蓝光的荧光棒。爸妈今晚加班,把他"暂存"在这里——一座堆满旧物的小阁楼,空气中漂浮着樟木和旧书页的味道。闪电划破窗外的黑暗,照亮墙角一摞摞落灰的纸箱,也照亮他面前那本被红绳捆着的古旧卷轴。
"It's just an old scroll," 他自言自语。十六岁的小码哥,是个痴迷编程的少年,他的房间里贴满了代码海报,床头放着 Raspberry Pi,梦里都在跑 Python 脚本。但眼前这卷用羊皮做底、墨迹泛黄的卷轴,看上去比他的年纪还要古老。封面上画着一只展翅的猫头鹰,爪下压着一把钥匙,眼眶里嵌着两枚闪烁的红宝石。
"The owls are not what they seem," 他念出封面下方一行褪色的小字。这是他最近追的一部美剧《Twin Peaks》里的台词——可不知为何,念出它的时候,他的心跳漏了半拍。
"啪嗒"一声,红绳自己松开了。
卷轴在他膝上缓缓展开,蓝光中浮现出奇异的符号:圆圈、方块、箭头,互相嵌套,仿佛一座微型城市的地图。最让他屏息的是一行手写体英文:
> *"When code becomes a forest, the patterns are the paths."* > > (当代码变成森林,模式就是道路。)
小码哥眨了眨眼。"模式"?"Pattern"?他立刻在脑子里调出 GitHub 上的搜索结果。设计模式,Design Patterns——他当然听过,但他一直以为那是面试要背的"八股文",是工作了才用得上的"高阶知识"。
可现在,那只猫头鹰的红宝石眼睛似乎在看着他的灵魂深处。
"想学吗?"
一个苍老而温柔的声音,从身后那棵比阁楼还高的老橡树里传出。
小码哥猛地转头。那棵橡树的树干上,一张慈祥的脸正从年轮里浮现——弯曲的眉、垂到树根的白胡须,眼眸深邃如镜,泛着萤火虫般的光。
"我叫 PaTern,"树灵微微颔首,"也叫做'模式之灵'。你祖父年轻时,曾在这里签下过一份契约。今天,这份契约找到了你。"
"A contract?" 小码哥瞪大了眼。
PaTern 笑了:"是的。一份关于代码、思维与未来的魔法契约。在你签下它之前,孩子,先让我告诉你——设计模式,到底是什么。"
二、模式不是招式,是思维契约 / Patterns Are Not Moves, They Are Thinking Contracts
"大多数初学者都有一个误解,"PaTern 树灵的声音低沉如风过树梢,"他们以为设计模式(Design Patterns)是某种'必杀技',是写代码时需要调用的一组'函数'。错了。 模式不是招式,而是契约。"
小码哥皱眉:"契约?听起来像合同法。"
"是合同法,"PaTern 点头,"一份由无数前辈程序员用数十年经验,与'反复出现的问题'签署的合同。每当你在写代码时遇到某个老问题——比如'这个对象我只想让它存在一个',或者'我需要让多个模块互相通信但又不想让它们纠缠不清'——前辈们就站起来说:'嗨,我也遇到过。我把它解决得不错,这是我的方案,签个名吧。'"
"这些方案,被写进了 1994 年的一本神书——由四位计算机科学家合著的 *Design Patterns: Elements of Reusable Object-Oriented Software*,由 Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides 共同撰写。"
"这四个人,被称为 'Gang of Four'——'四人帮',简称 GoF。"
小码哥立刻掏出他的小本本,荧光笔划下重点:"G-o-F,四人帮,1994 年。"
"对,"PaTern 继续,"这本书把过去几十年里被反复使用、证明有效的设计经验,提炼成了 23 个模式,划分为三大门派:"
PaTern 用树枝在空气中画出一道光痕,光痕里浮现出三个大字:
> 创建型(Creational) > 结构型(Structural) > 行为型(Behavioral)
"代码王国,就像一个真实世界里的王国,面临着三大难题:"
"第一,如何创造。要建造一座城堡,你需要砖块、瓦片、木头,但你怎么决定谁来生产它们?是工人自己烧砖,还是委托给专门的工厂?这是创建型模式要解决的问题。"
"第二,如何组装。你有了砖块和瓦片,但怎么把它们拼成一栋既坚固又美观的房子?是用强力胶(继承)粘连,还是用榫卯(组合)卡接?这是结构型模式要回答的问题。"
"第三,如何协作。房子建好了,住在里面的居民怎么交流?是挨家挨户串门(紧耦合),还是建立一个公告板让所有人订阅(解耦)?这是行为型模式的核心。"
"23 个模式,就是这三大难题的 23 种'参考答案'。它们不是唯一的答案,但它们是经过时间检验的好答案。"
小码哥咽了口唾沫。"所以我学这 23 个模式,其实是在学'怎么思考'?"
"聪明,"PaTern 微笑,"你触及了最关键的一点。模式是思维契约——它教给你的是看待问题的视角,是面对困境时大脑里'该往哪里想'的方向感。这比任何具体代码都重要。"
他停顿了一下,又补充道:
"记住,孩子,模式不是教条。它们可以被讨论、被演进、被情境化。在不同语言、不同框架、不同业务里,同一个模式可能长得完全不同。死记硬背模式的人,只能在面试里活过 30 秒;真正理解模式的人,能在真实世界里活过 30 年。"
三、为什么是 23 个?/ Why Exactly 23?
"可是,"小码哥忍不住问,"为什么偏偏是 23 个,不是 24,也不是 22?"
PaTern 笑了,胡须在风中飘荡。
"好问题。答案是——23 不是一个神圣的数字,它是一个'历史的偶然'。"
"在 1990 年代初期,Erich Gamma 在他的博士论文里开始整理模式。Richard Helm 和 Ralph Johnson 在伊利诺伊大学一起做了相关研究。John Vlissides 在 IBM 研究类似课题。他们四个人相识后,决定把各自的发现合并成一本'终极指南'。"
"经过反复讨论,他们发现:在面向对象编程的范畴内,反复出现、值得单独命名的设计问题,大约就是 23 个。多一个就显得啰嗦,少一个又觉得有所遗漏。23 这个数字,是他们在'覆盖度'和'精炼度'之间做出的平衡。"
小码哥若有所思:"所以,23 不是终点。"
"完全正确,"PaTern 说,"模式是一个'不断生长的家族'。在 GoF 之后,又出现了许多新的模式——比如并发模式、分布式模式、架构模式(MVC、MVVM、Microkernel、Event-Driven 等等)。但 GoF 23 个,是最经典、最常考、最值得入门的一组。它们就像数学里的'九九乘法表',是更复杂计算的基石。"
"现在,让我把这 23 个模式,画给你看。"
PaTern 用树枝敲了敲地面。空气中浮现出一张发光的地图——
```mermaid flowchart TD A[GoF 23 Design Patterns<br/>设计模式 23 传奇] --> B[Creational 创建型<br/>5 个] A --> C[Structural 结构型<br/>7 个] A --> D[Behavioral 行为型<br/>11 个] B --> B1[Singleton<br/>单例] B --> B2[Factory Method<br/>工厂方法] B --> B3[Abstract Factory<br/>抽象工厂] B --> B4[Builder<br/>生成器] B --> B5[Prototype<br/>原型] C --> C1[Adapter<br/>适配器] C --> C2[Bridge<br/>桥接] C --> C3[Composite<br/>组合] C --> C4[Decorator<br/>装饰器] C --> C5[Facade<br/>外观] C --> C6[Flyweight<br/>享元] C --> C7[Proxy<br/>代理] D --> D1[Chain of Responsibility<br/>责任链] D --> D2[Command<br/>命令] D --> D3[Interpreter<br/>解释器] D --> D4[Iterator<br/>迭代器] D --> D5[Mediator<br/>中介者] D --> D6[Memento<br/>备忘录] D --> D7[Observer<br/>观察者] D --> D8[State<br/>状态] D --> D9[Strategy<br/>策略] D --> D10[Template Method<br/>模板方法] D --> D11[Visitor<br/>访问者] ```
"这张图,就是你接下来 23 段旅程的地图,"PaTern 说,"你会遇见 5 位'创造者'——他们解决'对象如何诞生'的问题;7 位'建筑师'——他们解决'对象如何组合'的问题;11 位'协调者'——他们解决'对象如何对话'的问题。"
"在第 2 章,你会首先遇到 Single 独行侠——一个固执又迷人的家伙,他坚持'这个世界上只允许有一个我'。"
"在第 3 章,你会遇见 Factory Method 工厂长老——他从不亲自制造产品,只负责颁发'生产许可证'。"
"一个一个去认识他们吧。先见森林,再见树木。 先有全貌,再看细节。这就是地图的意义。"
四、设计的哲学:把变化的部分封装起来 / The Philosophy: Encapsulate What Varies
"在你离开阁楼之前,"PaTern 的眼神变得严肃,"我要告诉你一个贯穿 23 个模式的'共同咒语'。"
小码哥竖起耳朵。
"这个咒语,是 GoF 在他们书的第一章就埋下的种子:*Encapsulate the concept that varies.*"
他停顿一下,让小码哥自己翻译。
"把变化的部分封装起来,"小码哥轻声说。
"完美。"
"23 个模式,本质上都是在做同一件事——找到系统中'会变化'的部分,把它封装到一个独立的结构里,让其他部分不受影响。"
"比如:"
"- 如果你担心对象的'创建过程'会变化,那就把它封装成 Factory。" "- 如果你担心算法的'具体实现'会变化,那就把它封装成 Strategy。" "- 如果你担心对象之间的'通信方式'会变化,那就把它封装成 Observer。"
"所有模式都遵循这个核心哲学。再多模式、再多变化,万变不离其宗。"
小码哥沉默了一会儿,然后问了一个很深刻的问题:"那我应该在什么时候使用模式?"
PaTern 笑了,眼里满是赞许。
"好问题。三个时刻:"
"第一,当你发现自己在重复写相同的代码——这就是模式在向你招手。" "第二,当你预见到未来需求会变化——现在就封装变化,比将来重构便宜一百倍。" "第三,当你读不懂别人的代码时——也许他用的是某个模式,识别出它,你就看懂了他的设计意图。"
"反过来,不要为了用模式而用模式。如果你只有 3 行代码,强行套上 5 个模式,结果只会是灾难。记住 Kent Beck 那句名言:'先让它能工作,再让它漂亮。'"
为了让小码哥理解什么叫"封装变化",PaTern 在空气中写下一段 Python 代码——
```python
糟糕的代码:把变化的部分散落在各处
class Report: def __init__(self, data, format_type): self.data = data self.format_type = format_type
def generate(self): if self.format_type == "pdf": self._generate_pdf() elif self.format_type == "html": self._generate_html() elif self.format_type == "csv": self._generate_csv()
未来如果加 JSON 怎么办?必须改这个类
def _generate_pdf(self):
100 行 PDF 生成代码
pass
def _generate_html(self):
100 行 HTML 生成代码
pass
def _generate_csv(self):
100 行 CSV 生成代码
pass ```
"看,"PaTern 指着代码说,"这个 `Report` 类,把'如何生成不同格式'这个会变化的概念,硬生生地焊死在自己身上。每加一种格式,它就要被改动一次。这就是'变化的部分没有被封装'。"
"而当你学会了 Strategy 模式,你会变成这样——"
```python
改进的代码:把变化的部分封装成独立策略
from abc import ABC, abstractmethod
class ReportFormatter(ABC): """抽象策略:所有格式的共同契约""" @abstractmethod def format(self, data) -> str: pass
class PDFFormatter(ReportFormatter): def format(self, data) -> str: return f"<PDF>{data}</PDF>"
class HTMLFormatter(ReportFormatter): def format(self, data) -> str: return f"<HTML><body>{data}</body></HTML>"
class CSVFormatter(ReportFormatter): def format(self, data) -> str: return ",".join(data)
class Report: def __init__(self, data, formatter: ReportFormatter): self.data = data self.formatter = formatter # 变化的部分被注入进来
def generate(self) -> str: return self.formatter.format(self.data)
未来要加 JSON 格式?只需要新写一个类,Report 一行都不用改
```
"看到了吗?"PaTern 的声音变得轻柔,"`Report` 不再关心'具体格式是什么',它只关心'有人能帮我格式化'。变化的部分(具体格式)被封装到了独立的类里,新增格式时 `Report` 不需要修改。这就是'开闭原则'——对扩展开放,对修改关闭。"
小码哥若有所悟:"所以,模式不是一堆类,是一种思考方式——思考'哪些部分会变',然后'把它们隔离出去'。"
"你说到点子上了,"PaTern 点头,"这是所有 23 个模式共同的灵魂。"
五、契约的真正含义 / What the Contract Really Means
"现在,"PaTern 退后一步,卷轴重新悬浮在阁楼中央,"是时候签下契约了。"
小码哥看着卷轴下方那一行行空格,仿佛早已为他预留好了位置。
"这份契约里有什么?"他问。
"三个承诺,"PaTern 说:
"第一,你会保持好奇。 设计模式是一份不断演化的清单。今天的 23 个,明天可能是 30 个、50 个。但只要你保持好奇,你就永远不会掉队。"
"第二,你会保持怀疑。 模式不是圣经,模式是工具。当你发现某个模式不适用时,大声说出来,写下来,分享给社区。GoF 自己在序言里就强调:'*Use these patterns, but don't be their prisoner.*'(使用这些模式,但不要成为它们的囚徒。)"
"第三,你会保持谦卑。 你以为你懂了一个模式?再过一年,你会有更深的理解。学习是一个螺旋上升的过程,每一圈都更接近真理,但永远无法抵达。"
"你愿意签下这份契约吗?"
小码哥沉默了三秒。然后,他伸出手,握住了那支悬浮的羽毛笔。
"I do," 他说。
卷轴的空白处,自动浮现出他潦草的签名。瞬间,那只猫头鹰的红宝石眼睛里,迸发出金色的光——整本卷轴化作 23 道光柱,飞入小码哥的心口,化作一团温热的知识星云。
"欢迎,年轻的代码奇兵,"PaTern 向他鞠躬,"从今天起,你将不再是初学者。你将是一个能用模式思考的程序员。"
窗外雷声停了,月光洒进阁楼。
小码哥看着自己空空的双手,又看看那本已经消失的卷轴。
他笑了。
六、本书阅读地图:三件套的使用方法 / Your Reading Map: How to Use the Three-Piece Set
在正式踏上 23 段旅程之前,让我先给你画一张"装备清单"。
本书的每一个模式,都会以三件套形式呈现——
1. 故事(Story) 每个模式都会化身为一个有个性、有背景、有冲突的角色。你会在故事里看到他"为什么要出现","他解决了什么问题","他有什么性格缺陷"。故事不是为了娱乐——故事是把抽象概念人格化的工具。记住一个人的名字容易,记住一个概念难;记住一个故事里的人物命运,再记住他代表的概念,就容易一百倍。
**2. 代码(Cod