第六部分:新世界:作为协作与对话的代码

在C语言的“Bug谷”里用石头和木棍(以及goto和malloc)建造漏风小木屋的时代已经过去了。欢迎来到工业化革命。在第五章中,你,我们的时空旅行者,已经拿到了两座闪闪发光、马力强劲、充满蒸汽朋克风格的“OOP工厂”——C++和Java。
它们承诺了封装、继承和多态。它们承诺了你能用标准化的“对象”零件来建造“代码山”上的摩天大楼。

你兴奋地打开工厂大门,雇佣了100名程序员,对他们喊道:“开工!去给我造摩天大楼!”

一个月后,你回到了工厂。你看到的不是摩天大楼。你看到的是一个摇摇欲坠、由100种不同标准(但都自称是“对象”)的螺丝、齿轮和横梁强行拼接起来的“弗兰肯斯坦的城堡”。

欢迎来到第六章。你有了工厂,但你没有“标准作业程序”(SOPs)1

第六章:“设计模式”:建筑师的23份(及更多)标准蓝图

6.1 欢迎来到“对象工厂”,现在谁来管管这该死的流水线?

在你的BASIC时代,你最大的敌人是“意大利面条式代码”(Spaghetti Code),那些疯狂的GOTO跳转让你在自己的逻辑里迷了路1。而在90年代初的C++和Java时代,我们有了一个新敌人,一个更庞大的怪物:“架构噩梦”(Architectural Nightmare)2

问题出在哪里?问题出在“规模”和“协作”上。

当编程从一个“独行侠”的手艺活(C语言时代)转变为一个100人团队的“工业化生产”(OOP时代)时,技术问题就迅速演变成了“社会学”问题1。你的“对象工厂”本质上是一种工业化生产流程1。而工业化生产最核心的挑战是什么?是“标准化”和“协作”。

如果每个工人(程序员)都用自己“独创”的方式来制造“齿轮”,那么这些齿轮永远也拼不到一起。你需要的是一套所有人都同意的“蓝图”(Blueprints)1。你需要一套“标准作业程序”(SOPs)来组织这条全新的流水线。

设计模式的诞生,其首要驱动力并非来自程序员对技术“完美主义”的追求,而是来自“规模化”带来的“社会学”压力。我们需要一种方法,让100个(未来可能是1000个)程序员在不互相“掐架”的情况下,建造同一个东西。我们需要一种“通用语”(Common Language)1

6.2 “四人帮”(GoF)的密谋:一本(并非原创的)圣经是如何诞生的

就在90年代的这场架构混乱中,一本名为《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)的书横空出世3

这本书就像一块从天而降、刻着23条戒律的石板4。它迅速成为了“面向对象软件开发的基石”3,一本所有程序员都必须(或者至少假装)阅读过的“圣经”。

这本书的作者是四位(很快就声名显赫的)大学者和工程师:Erich Gamma, Richard Helm, Ralph Johnson, 和 John Vlissides3

说实话,这些名字又长又难记。当时的编程社区(在表达敬意方面总是那么“高效”)很快就给他们起了一个更上口、更具“革命性”的绰号:“四人帮”(Gang of Four, GoF)5

现在,揭晓本章的第一个关键启示,也是最大的“谎言”:这“四人帮”(GoF)(几乎)没有“发明”这些模式。

他们不是发明家。他们是“策展人”(Curators)和“分类学家”(Taxonomists)。

他们所做的,是观察、收集、命名和编纂那些早已在Smalltalk和C++社区中,被优秀程序员们反复使用的、但当时尚未被正式命名的“最佳实践”6。就像一位开发者后来回忆的那样,很多优秀的程序员“没有意识到他们已经使用已知的设计模式解决了问题……他们只是直观地使用了它们,因为这是解决问题的最佳方式”6

GoF的真正灵感,说出来你可能不信,并非来自计算机科学,而是来自一个(真正盖房子的)建筑师7

这位建筑师名叫 Christopher Alexander。他在1977年写了一本名为《A Pattern Language》(模式语言)的天才著作8。Alexander 提出一个激进的想法:伟大的建筑,无论是城镇、房屋还是窗户,都是由一系列可重复的“模式”组成的9。他(和他的合著者们)编纂了253个模式,比如“阳光充足的南向”(SUNNY SOUTH)或“不同高度的天花板”(CEILING HEIGHTS VARY)。

这场跨界联姻的火花,早在1987年就在 OOPSLA(面向对象编程、系统、语言与应用)会议上被点燃了。两位(后来的)大神——Kent Beck(敏捷开发的教父)和 Ward Cunningham(Wiki的发明者)——展示了如何将 Alexander 的想法应用于软件开发(具体来说,是用户界面)10

“四人帮”的种子则是在1990年的另一次 OOPSLA 会议上播下的,当时 Erich Gamma 和 Richard Helm 相遇,发现了彼此对“模式”的共同兴趣11。Erich Gamma 在1991年的博士论文7为这本书奠定了坚实的学术基础。最终,在1994年,这本“圣经”在 OOPSLA 会议上正式发布,彻底引爆了整个行业11

GoF的成功,是一场“时机”和“包装”的伟大胜利。在他们出书的同一年(1995年),同一家出版商(Addison-Wesley)还出版了另一本由 Wolfgang Pree 教授撰写的、关于设计模式的书,但GoF的书“使其相形见绌”7

为什么?因为GoF的贡献是决定性的:他们提供了“权威性的编纂”。他们将程序员“部落”中那些“只可意会不可言传”的“部落智慧”(Tribal Knowledge)6,转化为了可以被大规模传播和教学的“工业标准”(Industry Standard)1

他们给了社区一个“官方词典”。他们(武断地)选出了23个模式4,并将它们整齐地归为三大类:创建型(Creational)、结构型(Structural)和行为型(Behavioral)12

他们结束了(在模式领域的)“造词运动”,这正是在“工厂”里焦头烂额的管理者们所急需的。

6.3 核心论战:作为“通用语”的模式 vs 作为“法律”的UML

在90年代,为了驯服OOP这头刚被释放出来的、极其复杂的巨兽,编程界出现了两条截然不同的路径。这两条路径的冲突与结局,几乎决定了接下来30年软件开发的形态。

  1. 路径A(自下而上): 设计模式(Design Patterns)—— 一套灵活的“词汇表”。
  2. 路径B(自上而下): 统一建模语言(UML)—— 一套僵化的“法律文书”。

6.3.1 模式的幸存之道:程序员的“黑话”与“知识捷径”

这是本章的核心论点:设计模式的核心价值不是“代码”,而是作为“跨学科的交流媒介”的“词汇”。

模式的真正力量在于,它是一种高效的“沟通技巧”(Communication Skill)13和“通用词汇”(Common Vocabulary)13

想象一个场景:你和你的同事(另一位刚“复活”的旅行者)正站在一块白板前,为你们的“OOP工厂”设计一个新的功能1。你们的系统需要处理多种不同的支付方式(比如信用卡、PayPal、比特币)。

  • 没有“模式”的对话(低效的):你:“嗯……我想我们应该创建一个‘支付处理器’类。然后,也许在里面放一个if-else语句?if (type == 'credit_card') {... } else if (type == 'paypal') {... }?不不不,那样太难维护了。也许……我们应该定义一个PaymentInterface接口,它有一个pay()方法。然后我们创建CreditCardPayment和PaypalPayment两个类去实现这个接口。然后,‘支付处理器’类持有一个这个接口的引用,当需要支付时,我们就把正确的实例‘注入’进去……”
  • 拥有“模式”的对话(高效的):你:“这里用个‘策略模式’(Strategy Pattern)。”
    你的同事(秒懂):“好主意。”

看到了吗?设计模式,本质上是一种用于架构思想的“有损压缩算法”。

它允许两位工程师用极低的“带宽”(几个词,一句“黑话”)14,传递极高密度和复杂度的“设计意图”13

当你说出“策略模式”这个词时,这个词就像一个“指针”(Pointer)或“知识捷径”,它立刻指向你大脑中(以及你同事大脑中)存储的关于“如何封装一系列可互换算法”的那个共享的、完整的知识数据块13

因此,模式的价值不在于那几行“代码”——代码只是你(或者AI)“解压缩”后的产物。其价值在于那个“压缩包”本身——那个“词”(Word)1。它极大地提高了团队沟通的效率,是最高效、最专业的“程序员黑话”。

6.3.2 “抽象的诅咒”:UML的黄昏

就在GoF的“词汇表”开始流行的同时,另一场更血腥的战争正在肆虐:“方法战争”(The Method Wars)15

在80年代末到90年代初,面向对象(OO)建模的方法和语言爆炸性增长,从不到10种增加到了50多种15。每个“大师”都在极力推销自己的方法,都有自己的图形符号和理论体系,整个行业一团糟。

终于,有三位“大师”决定站出来,结束这场混乱15。他们是:

  1. Grady Booch(Rational Software公司的,Booch 方法的创造者)15
  2. Jim Rumbaugh(通用电气的,OMT - 对象建模技术的创造者)15
  3. Ivar Jacobson(爱立信的,OOSE - 面向对象软件工程 和 Use Cases(用例)概念的创造者)15

这三个人,在历史上被(亲切地)称为“三巨头”(The Three Amigos)15

他们在90年代中期联手15,试图将他们各自的方法(Booch, OMT, OOSE)融合成一个“统一的”标准。他们的“和平条约”在1997年被对象管理组织(OMG)采纳,这个“官僚主义巨兽”正式诞生了。它的名字叫:UML(统一建模语言)15

UML的承诺是美好的:成为一种“通用的、被广泛支持的建模语言”15,用一套标准的图形化方式来设计软件1

但现实是残酷的。UML迅速催生了一个庞大的“UML培训、认证、工具”产业1。公司花了数百万美元培训员工如何绘制类图、序列图、活动图……结果呢?

正如有人尖锐指出的:实践证明,“在UML中设计软件是笨拙的、不灵活的、限制性的和缓慢的”。

这就是“法律”与“词汇”的对决。

  • 为什么UML(在很大程度上)失败了? 因为UML被当作“法律”(The Law)来推行1。它是一种僵化的、自上而下的设计官僚主义。它鼓励“前期大型设计”(Big Design Up Front),要求你在写代码之前就绘制出几十张完美的图表。这种哲学,与几乎同时期从实践中(同样由OO专家们)发展出来的“敏捷编程”(Agile Programming)16和“极限编程”(Extreme Programming)16的迭代、小步快跑的精神背道而驰。
  • 为什么“设计模式”胜出了? 因为模式是“词汇”(The Vocabulary)1。它是描述性的(Descriptive),而非规定性的(Prescriptive)。它足够灵活,可以被用在“迭代的实践”中1

最终的讽刺性判决是:“模式作为‘词汇’幸存下来,而UML作为‘法律’则(在很大程度上)死去了,证明了僵化的顶层设计永远敌不过灵活的、迭代的实践。”1

6.4 深入蓝图:让我们(幽默地)解剖几个关键模式

好了,旅行者,欢迎来到“军火库”。UML那套繁文缛节的“法律”已经被扔进了历史的垃圾堆,但GoF的“词汇表”12却幸存了下来。

你不需要背诵所有23个模式。你只需要理解它们的核心“精神”——它们承诺解决什么问题,以及(更重要的)它们会带来什么“隐藏的陷阱”。我们将为你展示GoF的三类武器:创建型、结构型 和 行为型12

这是一份为你准备的“时空旅行者速查备忘录”。

模式 (Pattern) 类别 (Category) “一句话承诺” (The Promise) “黑话”比喻 (The Witty Analogy) “隐藏的陷阱” (The Hidden Trap)
单例 (Singleton) 创建型 确保一个类只有一个实例1 “独裁者”或“空管塔台”17 “美化的全局变量”18;臭名昭著的“反模式”19
工厂 (Factory) 创建型 帮你创建对象,隐藏“new”1 “披萨店”或“宜家主题套餐” “新型官僚主义”20;过度抽象21
适配器 (Adapter) 结构型 让不兼容的接口协同工作22 “全球通用电源插头适配器”23 几乎没有。它就是这么一个老实(且有用)的模式。
装饰器 (Decorator) 结构型 动态地给对象添加新功能24 “俄罗斯套娃”25或“穿衣服”26 可能会创造出一堆“小包装类”,导致类爆炸。
外观 (Facade) 结构型 为复杂子系统提供简单入口27 “客服总机”或“餐厅点餐服务员”28 可能会隐藏太多细节,导致调试困难。
观察者 (Observer) 行为型 "订阅"一个对象的状态变化1 “八卦专栏”或“油管(YouTube)频道订阅”29 “失效监听器”导致的内存泄漏(Memory Leak)30
策略 (Strategy) 行为型 封装一系列可互换的算法31 “地图APP”里的“导航算法”32 适用于算法,但容易被滥用于一切。

6.4.1 创造型模式:我们从哪儿搞到这些该死的东西? (Creational Patterns)

这类模式回答了一个问题:“我该如何(优雅地)new一个对象,而不用把new关键字搞得满地都是?”

A. 单例模式 (Singleton) —— 万恶之源还是“美化的全局变量”?

  • 它的承诺: “确保一个类只有一个实例,并提供一个全局访问点”1
  • 听起来不错的比喻: “政府”(The Government)33。一个国家只能有一个(官方)政府。“空中交通管制塔”(Air Traffic Control Tower)17。你绝对不希望有两个塔台同时指挥飞机,那会变成一团糟(至少理论上是这样)。
  • 它如何变成“反模式”: 很快,程序员们就发现了这玩意的“真面目”。这玩意儿不就是个“美化的全局变量”(a glorified global variable)吗?18
  • 程序员的“原罪”:懒惰 (Laziness): 人们为什么如此热爱(并滥用)它?两个字:“懒惰”34。一位(非常诚实的)开发者在Reddit上是这么说的34:“(单例模式)通常是支持懒惰的论据。开发者不想把相关信息传来传去。他们希望无论在任何地方的任何函数里,都能神奇地从空气中把它抓取出来。”
  • 那个(必须引用的)经典吐槽: 这就催生了关于单例模式最经典的吐槽,它完美概括了这种模式的荒谬性34

    新手程序员: “我们需要一个数据库连接。”
    (刚读完GoF的)架构师(两眼放光): “天哪!(WELP!) 我猜我们最好把这个连接句柄放进一个有十三层安保的保险库里,确保有史以来只有这一个连接被使用!”

  • 真正的灾难: 这个“只有一个数据库连接”的例子,是一个“非常糟糕的主意”(Bad idea)35。你今天(天真地)以为你“只需要一个”,并把它做成了单例35。明天,你的老板告诉你,我们需要一个“数据库连接池”来提高性能35。后天,另一个老板说我们要为Web界面的每一个并发用户提供一个独立的连接35
    完了。你被焊死(Tightly Coupled)在这个“全局”实例上了36。你的代码现在极度依赖这个“全局状态”37,导致单元测试变成了地狱(因为你无法模拟或替换这个全局实例)36,并且它还违反了几乎所有的设计原则(比如单一职责原则)36
  • 最终裁决: 在大多数情况下,单例模式是一个“反模式”(Anti-Pattern)19。如果你“只需要一个”,那就(在程序入口)“只创建一个”,然后把它作为参数传递下去(这叫“依赖注入”)。不要用一个“全局独裁者”来污染你的整个代码库。

B. 工厂模式 (The Factory) —— 从“简单披萨店”到“官僚主义地狱”

  • 它的承诺: 定义一个用于创建对象的接口,但让子类(或实现类)来决定到底要实例化哪一个具体的类1
  • 它解决的问题: 你的代码里充斥着这样的“丑陋”逻辑:38
    // 丑陋的、需要重构的代码
    if (orderType == "pizza") {
       item = new Pizza();
    } else if (orderType == "burger") {
       item = new Burger();
    } else if (orderType == "soda") {
       item = new Soda();
    }
    
    这个巨大的if-else或switch语句是“万恶之源”。
  • Level 1: 简单工厂 (Simple Factory)38
    • 这甚至不是一个“官方”的GoF模式,但它可能是最有用的。
    • 比喻: “餐厅前台”。
    • 做法: 你把上面那段丑陋的switch语句,原封不动地搬到一个专门的FoodFactory类里。你的主程序不再关心“如何创建”,它只管告诉“前台”:“给我来一份‘pizza’”。
    • item = FoodFactory.createFood("pizza");
    • 干净、利落。你把“创建”的职责(How)和“使用”的职责(When)分开了。
  • Level 2: 工厂方法 (Factory Method)38
    • 这是“官方”GoF模式。它使用“继承”。
    • 比喻: “汉堡王 vs 素食堡专卖店”39
    • 做法: 你定义一个抽象的BurgerRestaurant(基类),它有一个抽象方法createBurger()。然后,BeefBurgerRestaurant(子类)重写这个方法,返回new BeefBurger()。而VeggieBurgerRestaurant(子类)则返回new VeggieBurger()。
  • Level 3: 抽象工厂 (Abstract Factory)38
    • 这是工厂模式的“终极形态”,也是最容易被滥用(过度工程化)的形态。
    • 关键区别: 它不是用来创建一个产品,而是用来创建一“族”(family)相关的产品40
    • 最好的比喻: “宜家主题套餐”。
    • 场景: 你需要一套家具,而且必须保证“风格统一”。
    • 抽象工厂接口(FurnitureFactory): 它定义了三个方法:createChair(), createTable(), createLamp()。
    • 具体工厂A(NordicStyleFactory): 它负责创建“北欧风格”全家桶。它返回 NordicChair, NordicTable, NordicLamp。
    • 具体工厂B(IndustrialStyleFactory): 它负责创建“工业风格”全家桶。它返回 IndustrialChair, IndustrialTable, IndustrialLamp。
    • 重点: 这个模式的唯一目的,是保证“一致性”。你永远不会(意外地)得到一个“北欧风格的椅子”和一个“工业风格的桌子”。
  • “工厂”的诅咒: 工厂模式是对“抽象”的完美体现,但它也是“过度工程化”(Over-engineering)和“新官僚主义”(New Bureaucracy)的温床20。为了创建三个对象,你现在(在抽象工厂里)可能需要一个接口和两个新的具体类。你的代码库里很快就充满了各种只做一件事的“工厂类”、“抽象工厂接口”、“工厂的工厂”……你解决了“new”的问题,却创造了一个“官僚主义”的问题20

6.4.2 结构型模式:胶水、胶带和化妆盒 (Structural Patterns)

这类模式回答了一个问题:“我该如何把这些‘对象’(类)组装成一个更大、更合理的结构?”

A. 适配器模式 (Adapter)

  • 它的承诺: 作为两个“不兼容接口”之间的“包装器”(Wrapper)或“翻译器”,让它们可以协同工作22
  • 最完美的比喻: “全球通用电源插头适配器”23
  • 场景: 你,我们的时空旅行者,带着你的美国笔记本电脑(它有一个US_Plug接口)去德国旅行23。你悲催地发现,德国的墙上是German_Socket(德标插座)接口。你不能直接把它插进去。
  • 解决方案: 你在机场买了一个“电源适配器”(PowerAdapter)23。这个适配器(Adapter)实现了German_Socket接口(所以墙很高兴,允许它被插入),同时它在内部*包装(持有)*了你的US_Plug对象(所以你的笔记本电脑也很高兴,它被插了进去)。问题解决。
  • 裁决: 可能是GoF模式中最有用、最直观、几乎没有争议的模式。它就是用来“粘合”那些你无法修改的(比如第三方库或遗留代码)接口的“万能胶水”。

B. 装饰器模式 (Decorator)

  • 它的承诺: 允许你“动态地”(在运行时)向一个对象添加新的行为,而不是通过(在编译时)“继承”来创建子类24
  • 最完美的比喻: “穿衣服”26
  • 场景: 你是一个Human对象。天冷了。
  • (糟糕的)继承方案: 你创建一个HumanWithSweater子类。如果又下雨了,你再创建一个HumanWithSweaterAndRaincoat子类。如果……你很快就陷入了“类爆炸”(Class Explosion)。
  • (优雅的)装饰器方案: 你动态地“装饰”你自己26
    1. human = new Human()
    2. human = new Sweater(human) // 穿上毛衣
    3. human = new Jacket(human) // 再套上夹克
    4. human = new Raincoat(human) // 最后穿上雨衣
      Sweater, Jacket, Raincoat 都和 Human 实现了同一个接口(比如IWearable),它们一层一层地把原对象“包”了起来。
  • 另一个比喻: “俄罗斯套娃”(Russian Doll)25。一个对象套一个对象套一个对象。如果你用过Java的I/O流,你一定见过这个“噩梦”:new BufferedReader(new FileReader(new File(...)))25。恭喜你,你早就用过装饰器模式了。

C. 外观模式 (Facade)

  • 它的承诺: 为一个复杂的子系统(或者一堆乱七八糟、互相纠缠的类库)提供一个“简化的(但有限的)”接口27
  • 最完美的比喻: “客服总机”或“餐厅点餐服务员”28
  • 场景: 你打电话给一家大公司28。你不需要知道“账单部”的分机号、“仓库部”的经理是谁、“物流部”的系统用的是什么数据库。
  • 解决方案: 你只打给“总机接线员”(The Facade)。你告诉他(她):“我要下单。” 这个接线员为你提供了“简单的语音接口”(Simple Interface)28,然后他(她)自己去协调背后所有复杂的子系统(账单、仓库、物流)28
  • 另一个绝佳比喻: 信用卡支付41。你(客户端)的代码非常简单:walletFacade.pay(amount)
    这个walletFacade(外观)在幕后(可能)完成了几十个复杂的操作:account.checkBalance()(检查账户)、securityCode.checkPIN()(检查密码)、wallet.debit()(扣款)、ledger.makeEntry()(记账)、notification.sendSMS()(发短信)41
    外观模式就是你(和你的团队)的“理智防火墙”,它把“复杂性”隐藏在幕后。

6.4.3 行为型模式:它们到底在聊什么? (Behavioral Patterns)

这类模式是关于“通信”的。它们回答了一个问题:“这些对象之间,该如何(优雅地)互相‘聊天’和‘协作’?”

A. 观察者模式 (Observer) —— “订阅我!”(以及你如何因此泄露内存)

  • 它的承诺: 定义一种“一对多”的依赖关系1。当一个对象(“主题”Subject,或称“被观察者”)改变状态时,所有依赖(“订阅”)它的对象(“观察者”Observers)都会自动得到通知并更新1
  • 比喻: “八卦杂志”或“油管(YouTube)频道主”(Subject)和它的“订阅者”(Observers)42
    • 你(Observer)点击“订阅”(subject.attach(this)29
    • 频道主(Subject)发布了新视频(stateChange)。
    • 频道主(Subject)调用notify()方法,遍历它的“订阅者列表”,挨个通知你们:“嘿,更新了!”
    • 你(Observer)收到通知后,自己去获取最新视频(update())。
  • 现代的诅咒(之一):“失效监听器问题” (The "Lapsed Listener Problem")30
    • 这是观察者模式在现代垃圾回收语言(如Java, C#)中一个极其阴险的“内存泄漏”(Memory Leak)陷阱43
    • 泄漏机制(请仔细阅读!):
      1. 观察者模式要求“订阅者”(比如一个UI窗口)显式地“注册”(Attach/Subscribe)到“主题”(比如一个数据模型)上29
      2. 为了能在未来通知你,“主题”必须持有一个列表,其中包含对你(UI窗口)的“强引用”(Strong Reference)30
      3. 现在,假设用户关闭了这个UI窗口。这个窗口对象理应被垃圾回收器(GC)回收。
      4. 但是! 如果这个UI窗口在关闭时,它的代码“忘记”了调用subject.detach(this)来“取消订阅”(Deregister / Unsubscribe)42……
      5. ……那么“主题”的“订阅者列表”中,仍然“牢牢地”拽着对这个UI窗口的强引用30
      6. 灾难发生: GC无法回收这个“已死”的UI窗口的内存。它(以及它引用的所有其他对象)将作为“僵尸”永远留在内存中,直到“主题”本身被销毁(可能直到程序关闭)44
      7. 这就是“失效监听器问题”(Lapsed Listener Problem)45。你“订阅”了,但你忘了“取消订阅”。
  • 现代的诅咒(之二):“事件风暴” (Event Storms)46
    • 如果你的“主题”状态变化得太快(比如,一个监视器每秒钟“翻转”1000次状态),它就会向所有的(可能成百上千个)观察者发送海量的通知,瞬间压垮系统,造成“性能瓶颈”46

B. 策略模式 (Strategy) —— 把你的“if-else”语句丢进垃圾桶

  • 它的承诺: 将“一系列行为(算法)”转换成“对象”,并使它们在“原始上下文对象”内部可以“互换”31
  • 它解决的问题: 它是你用来消灭“庞大的条件语句”(massive conditional statement)的终极武器32。你不再需要写那个“丑陋”的if (mode == 'walk') {... } else if (mode == 'bus') {... }了。
  • 最完美的比喻: “地图APP的导航算法”32
  • 场景: 你要从A点到B点32
  • 解决方案
    1. 你定义一个RouteStrategy(策略)接口,它有一个buildRoute(A, B)方法。
    2. 你创建三个“具体策略”类:WalkingStrategy(步行策略)、RoadStrategy(驾车策略)、PublicTransportStrategy(公交策略)32
    3. 你的Navigator(上下文)类,持有一个RouteStrategy接口的引用32
    4. 当用户点击“步行”按钮时,你调用:navigator.setStrategy(new WalkingStrategy())
    5. 当用户点击“驾车”按钮时,你调用:navigator.setStrategy(new RoadStrategy())
    6. Navigator类本身的代码完全不用改。它只管调用currentStrategy.buildRoute(A, B)32
  • 另一个比喻: 电子商务中的“支付方式”47。你的“购物车”(Context)持有一个PaymentStrategy。你(用户)可以选择new PaypalStrategy()new CreditCardStrategy()48。策略模式是“开闭原则”(Open/Closed Principle)最优雅的体现:对扩展开放(你可以随便添加新的支付方式),对修改关闭(你永远不用碰购物车的主逻辑)。

6.5 结论:旧世界的蓝图,新世界的“提示词”

旅行者,让我们重申最终判决1

在90年代那场“驯服OOP”的战争中,UML,那个“僵化的顶层设计”,试图成为“法律”,它(在很大程度上)死去了。而设计模式,作为“灵活的、迭代的实践”,作为“词汇”幸存了下来。

在90年代,你需要这些“蓝图”1来手动、逐行地搭建你的OOP工厂。

而在今天,这个结论变得比以往任何时候都更加重要。

我们即将进入(或者说已经进入)第十六章的“Vibe编程”(Vibe Coding)时代——一个你通过“提示词”(Prompts)来指挥AI为你编程的时代1

在这个新世界里,你(时空旅行者)不再需要手动去写一个Singleton的所有“双重检查锁定”(Double-Checked Locking)的恶心代码。

但是,你必须知道Singleton这个词1

你的新工作,是(在更高的抽象层上)告诉你的AI“副驾驶”(AI Copilot)1

“听着,伙计。我需要你为这个复杂的API构建一个外观(Facade)。这个外观需要访问我们的日志系统,确保日志记录器(Logger)是一个单例(Singleton)。另外,支付模块必须使用策略模式(Strategy)来处理PayPal和信用卡这两种渠道,并且用一个适配器(Adapter)去接入那个老旧的银行SOAP接口。哦,对了,当支付成功时,使用观察者模式(Observer)通知购物车和订单服务。”

如果你不掌握GoF的这套“词汇”,你就无法与AI(或你的同事)进行这种高效的“架构级”沟通。你只能“凭感觉编程”(Vibe Coding)1,对AI说:“呃……你懂的,让它能付钱就行。” 然后,你就会得到一堆AI“凭感觉”生成的、完全无法维护的垃圾。

因此,GoF的这本“圣经”1,其价值非但没有过时,反而变得更加重要。它不再是你必须(逐行)遵守的“法律”,而是你(作为架构师)必须掌握的“通用语”1

欢迎来到新世界。你的“蓝图”已经进化成了“咒语”。


引用的著作


  1. 程序员复活手册:从BASIC到AI(大纲) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Perfect class examples : r/SCP - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/SCP/comments/ra7eeo/perfect_class_examples/ ↩︎

  3. 访问时间为 十月 27, 2025, https://www.digitalocean.com/community/tutorials/gangs-of-four-gof-design-patterns#:~:text=Authored%20by%20Erich%20Gamma%2C%20Richard,of%20object%2Doriented%20software%20development↩︎ ↩︎ ↩︎

  4. Gang of 4 Design Patterns Explained: Creational, Structural, and Behavioral | DigitalOcean, 访问时间为 十月 27, 2025, https://www.digitalocean.com/community/tutorials/gangs-of-four-gof-design-patterns ↩︎ ↩︎

  5. Gang of Four Design Patterns - Spring Framework Guru, 访问时间为 十月 27, 2025, https://springframework.guru/gang-of-four-design-patterns/ ↩︎

  6. dotDoThatDotConfigDotDone : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/1e0jjs7/dotdothatdotconfigdotdone/ ↩︎ ↩︎ ↩︎

  7. The Gang Of Four - De Programmatica Ipsum, 访问时间为 十月 27, 2025, https://deprogrammaticaipsum.com/the-gang-of-four/ ↩︎ ↩︎ ↩︎

  8. Pattern language - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Pattern_language ↩︎

  9. Design Patterns Elements of Reusable Object-Oriented Software - Javier8a.com, 访问时间为 十月 27, 2025, https://www.javier8a.com/itc/bd1/articulo.pdf ↩︎

  10. What Are Design Patterns? History, Origins, and Software Development Impact, 访问时间为 十月 27, 2025, https://blog.rheinwerk-computing.com/design-patterns-history-origins-and-impact ↩︎

  11. Design Patterns - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Design_Patterns ↩︎ ↩︎

  12. Gang of Four (GOF) Design Patterns - GeeksforGeeks, 访问时间为 十月 27, 2025, https://www.geeksforgeeks.org/system-design/gang-of-four-gof-design-patterns/ ↩︎ ↩︎ ↩︎

  13. Using Design Patterns as Communication Skill :: Adam Swiderski, 访问时间为 十月 27, 2025, https://swiderski.tech/2024-10-13-Design-patterns-as-communication-skill/ ↩︎ ↩︎ ↩︎ ↩︎

  14. A Case Against Coding Lingo. Because naming things doesn't have to… | by Frank de Jonge | Medium, 访问时间为 十月 27, 2025, https://medium.com/@frankdejonge/a-case-against-coding-lingo-8ffae1a4fa4e ↩︎

  15. The Unified Modeling Language, 访问时间为 十月 27, 2025, https://cs.smu.ca/~porter/csc/341/notes/uml.html ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  16. The Three Amigos, Among Others - De Programmatica Ipsum, 访问时间为 十月 27, 2025, https://deprogrammaticaipsum.com/the-three-amigos-among-others/ ↩︎ ↩︎

  17. Why the Singleton Pattern is Both Useful and Dangerous | by Maxim Gorin | Medium, 访问时间为 十月 27, 2025, https://maxim-gorin.medium.com/why-the-singleton-pattern-is-both-useful-and-dangerous-1c32bdf688f0 ↩︎ ↩︎

  18. Singletons Must Die - Yegor Bugayenko, 访问时间为 十月 27, 2025, https://www.yegor256.com/2016/06/27/singletons-must-die.html ↩︎ ↩︎

  19. Why is Singleton considered an anti-pattern? [duplicate] - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/12755539/why-is-singleton-considered-an-anti-pattern ↩︎ ↩︎

  20. Patterns of Industrial Bureaucracy - Gwern, 访问时间为 十月 27, 2025, https://gwern.net/doc/sociology/technology/1954-gouldner-patternsofindustrialbureaucracy.pdf ↩︎ ↩︎ ↩︎

  21. How much enterprise software is just the senior dev going in circles - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ExperiencedDevs/comments/1jvo1uo/how_much_enterprise_software_is_just_the_senior/ ↩︎

  22. Adapter in C# / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/adapter/csharp/example ↩︎ ↩︎

  23. Adapter - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/adapter ↩︎ ↩︎ ↩︎ ↩︎

  24. Decorator in Python / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/decorator/python/example ↩︎ ↩︎

  25. Two Design Patterns You're Probably Already Using | 8th Light, 访问时间为 十月 27, 2025, https://8thlight.com/insights/two-design-patterns-youre-probably-already-using ↩︎ ↩︎ ↩︎

  26. Decorator - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/decorator ↩︎ ↩︎ ↩︎

  27. Facade in Python / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade/python/example ↩︎ ↩︎

  28. Facade - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  29. Design Patterns: Observer Pattern - 2020 - BogoToBogo, 访问时间为 十月 27, 2025, https://www.bogotobogo.com/DesignPatterns/observer.php ↩︎ ↩︎ ↩︎

  30. Observer pattern - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Observer_pattern ↩︎ ↩︎ ↩︎ ↩︎

  31. Strategy in C# / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/csharp/example ↩︎ ↩︎

  32. Strategy - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  33. Singleton - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/singleton ↩︎

  34. Singleton - Pattern or anti-pattern? : r/programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/31iex3/singleton_pattern_or_antipattern/ ↩︎ ↩︎ ↩︎

  35. language agnostic - Most common examples of misuse of singleton ..., 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/135347/most-common-examples-of-misuse-of-singleton-class ↩︎ ↩︎ ↩︎ ↩︎

  36. Why Singleton Pattern is considered as anti-design pattern ? | by Sandesh Gaonkar | AIA Singapore Technology Blog | Medium, 访问时间为 十月 27, 2025, https://medium.com/aia-sg-techblog/why-singleton-pattern-is-considered-as-anti-design-pattern-c81dd8b7e757 ↩︎ ↩︎ ↩︎

  37. The Singleton; a Dangerously Overused Pattern | by Tim Williams - Medium, 访问时间为 十月 27, 2025, https://timjwilliams.medium.com/the-singleton-a-dangerously-overused-pattern-d4007758bca2 ↩︎

  38. Factory Comparison - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/factory-comparison ↩︎ ↩︎ ↩︎ ↩︎

  39. Abstract Factory: One Pattern, Many Clean Solutions - Maxim Gorin - Medium, 访问时间为 十月 27, 2025, https://maxim-gorin.medium.com/abstract-factory-one-pattern-many-clean-solutions-d1d4c55f4a04 ↩︎

  40. What are the differences between Abstract Factory and Factory design patterns?, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/5739611/what-are-the-differences-between-abstract-factory-and-factory-design-patterns ↩︎

  41. Facade in Go / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade/go/example ↩︎ ↩︎

  42. The Observer Pattern in Java | Baeldung, 访问时间为 十月 27, 2025, https://www.baeldung.com/java-observer-pattern ↩︎ ↩︎

  43. Game Dev: Unity/C# — What is the Observer Pattern? | by Ethan Martin - Dev Genius, 访问时间为 十月 27, 2025, https://blog.devgenius.io/game-dev-unity-c-what-is-the-observer-pattern-4ed647df4993 ↩︎

  44. Lapsed listener problem - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Lapsed_listener_problem ↩︎

  45. Observer · Design Patterns Revisited - Game Programming Patterns, 访问时间为 十月 27, 2025, https://gameprogrammingpatterns.com/observer.html ↩︎

  46. Microsoft System Center: Operations Manager Field Experience, 访问时间为 十月 27, 2025, https://download.microsoft.com/download/8/2/8/828C05A2-E6A0-436A-9AE1-704A8005E355/9780735695825.pdf ↩︎ ↩︎

  47. Strategy in PHP / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/php/example ↩︎

  48. Strategy in Java / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/java/example ↩︎