第六部分:新世界:作为协作与对话的代码
在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年软件开发的形态。
- 路径A(自下而上): 设计模式(Design Patterns)—— 一套灵活的“词汇表”。
- 路径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。他们是:
- Grady Booch(Rational Software公司的,Booch 方法的创造者)15
- Jim Rumbaugh(通用电气的,OMT - 对象建模技术的创造者)15
- 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-else或switch语句是“万恶之源”。
// 丑陋的、需要重构的代码 if (orderType == "pizza") { item = new Pizza(); } else if (orderType == "burger") { item = new Burger(); } else if (orderType == "soda") { item = new Soda(); } - 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:
human = new Human()human = new Sweater(human)// 穿上毛衣human = new Jacket(human)// 再套上夹克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())。
- 你(Observer)点击“订阅”(
- 现代的诅咒(之一):“失效监听器问题” (The "Lapsed Listener Problem")30
- 这是观察者模式在现代垃圾回收语言(如Java, C#)中一个极其阴险的“内存泄漏”(Memory Leak)陷阱43。
- 泄漏机制(请仔细阅读!):
- 观察者模式要求“订阅者”(比如一个UI窗口)显式地“注册”(Attach/Subscribe)到“主题”(比如一个数据模型)上29。
- 为了能在未来通知你,“主题”必须持有一个列表,其中包含对你(UI窗口)的“强引用”(Strong Reference)30。
- 现在,假设用户关闭了这个UI窗口。这个窗口对象理应被垃圾回收器(GC)回收。
- 但是! 如果这个UI窗口在关闭时,它的代码“忘记”了调用
subject.detach(this)来“取消订阅”(Deregister / Unsubscribe)42…… - ……那么“主题”的“订阅者列表”中,仍然“牢牢地”拽着对这个UI窗口的强引用30。
- 灾难发生: GC无法回收这个“已死”的UI窗口的内存。它(以及它引用的所有其他对象)将作为“僵尸”永远留在内存中,直到“主题”本身被销毁(可能直到程序关闭)44。
- 这就是“失效监听器问题”(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。
- 解决方案:
- 你定义一个
RouteStrategy(策略)接口,它有一个buildRoute(A, B)方法。 - 你创建三个“具体策略”类:
WalkingStrategy(步行策略)、RoadStrategy(驾车策略)、PublicTransportStrategy(公交策略)32。 - 你的
Navigator(上下文)类,持有一个RouteStrategy接口的引用32。 - 当用户点击“步行”按钮时,你调用:
navigator.setStrategy(new WalkingStrategy())。 - 当用户点击“驾车”按钮时,你调用:
navigator.setStrategy(new RoadStrategy())。 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。
欢迎来到新世界。你的“蓝图”已经进化成了“咒语”。
引用的著作
-
程序员复活手册:从BASIC到AI(大纲) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Perfect class examples : r/SCP - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/SCP/comments/ra7eeo/perfect_class_examples/ ↩︎
-
访问时间为 十月 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. ↩︎ ↩︎ ↩︎
-
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 ↩︎ ↩︎
-
Gang of Four Design Patterns - Spring Framework Guru, 访问时间为 十月 27, 2025, https://springframework.guru/gang-of-four-design-patterns/ ↩︎
-
dotDoThatDotConfigDotDone : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/1e0jjs7/dotdothatdotconfigdotdone/ ↩︎ ↩︎ ↩︎
-
The Gang Of Four - De Programmatica Ipsum, 访问时间为 十月 27, 2025, https://deprogrammaticaipsum.com/the-gang-of-four/ ↩︎ ↩︎ ↩︎
-
Pattern language - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Pattern_language ↩︎
-
Design Patterns Elements of Reusable Object-Oriented Software - Javier8a.com, 访问时间为 十月 27, 2025, https://www.javier8a.com/itc/bd1/articulo.pdf ↩︎
-
What Are Design Patterns? History, Origins, and Software Development Impact, 访问时间为 十月 27, 2025, https://blog.rheinwerk-computing.com/design-patterns-history-origins-and-impact ↩︎
-
Design Patterns - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Design_Patterns ↩︎ ↩︎
-
Gang of Four (GOF) Design Patterns - GeeksforGeeks, 访问时间为 十月 27, 2025, https://www.geeksforgeeks.org/system-design/gang-of-four-gof-design-patterns/ ↩︎ ↩︎ ↩︎
-
Using Design Patterns as Communication Skill :: Adam Swiderski, 访问时间为 十月 27, 2025, https://swiderski.tech/2024-10-13-Design-patterns-as-communication-skill/ ↩︎ ↩︎ ↩︎ ↩︎
-
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 ↩︎
-
The Unified Modeling Language, 访问时间为 十月 27, 2025, https://cs.smu.ca/~porter/csc/341/notes/uml.html ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
The Three Amigos, Among Others - De Programmatica Ipsum, 访问时间为 十月 27, 2025, https://deprogrammaticaipsum.com/the-three-amigos-among-others/ ↩︎ ↩︎
-
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 ↩︎ ↩︎
-
Singletons Must Die - Yegor Bugayenko, 访问时间为 十月 27, 2025, https://www.yegor256.com/2016/06/27/singletons-must-die.html ↩︎ ↩︎
-
Why is Singleton considered an anti-pattern? [duplicate] - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/12755539/why-is-singleton-considered-an-anti-pattern ↩︎ ↩︎
-
Patterns of Industrial Bureaucracy - Gwern, 访问时间为 十月 27, 2025, https://gwern.net/doc/sociology/technology/1954-gouldner-patternsofindustrialbureaucracy.pdf ↩︎ ↩︎ ↩︎
-
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/ ↩︎
-
Adapter in C# / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/adapter/csharp/example ↩︎ ↩︎
-
Adapter - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/adapter ↩︎ ↩︎ ↩︎ ↩︎
-
Decorator in Python / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/decorator/python/example ↩︎ ↩︎
-
Two Design Patterns You're Probably Already Using | 8th Light, 访问时间为 十月 27, 2025, https://8thlight.com/insights/two-design-patterns-youre-probably-already-using ↩︎ ↩︎ ↩︎
-
Decorator - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/decorator ↩︎ ↩︎ ↩︎
-
Facade in Python / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade/python/example ↩︎ ↩︎
-
Facade - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Design Patterns: Observer Pattern - 2020 - BogoToBogo, 访问时间为 十月 27, 2025, https://www.bogotobogo.com/DesignPatterns/observer.php ↩︎ ↩︎ ↩︎
-
Observer pattern - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Observer_pattern ↩︎ ↩︎ ↩︎ ↩︎
-
Strategy in C# / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/csharp/example ↩︎ ↩︎
-
Strategy - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Singleton - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/singleton ↩︎
-
Singleton - Pattern or anti-pattern? : r/programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/31iex3/singleton_pattern_or_antipattern/ ↩︎ ↩︎ ↩︎
-
language agnostic - Most common examples of misuse of singleton ..., 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/135347/most-common-examples-of-misuse-of-singleton-class ↩︎ ↩︎ ↩︎ ↩︎
-
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 ↩︎ ↩︎ ↩︎
-
The Singleton; a Dangerously Overused Pattern | by Tim Williams - Medium, 访问时间为 十月 27, 2025, https://timjwilliams.medium.com/the-singleton-a-dangerously-overused-pattern-d4007758bca2 ↩︎
-
Factory Comparison - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/factory-comparison ↩︎ ↩︎ ↩︎ ↩︎
-
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 ↩︎
-
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 ↩︎
-
Facade in Go / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/facade/go/example ↩︎ ↩︎
-
The Observer Pattern in Java | Baeldung, 访问时间为 十月 27, 2025, https://www.baeldung.com/java-observer-pattern ↩︎ ↩︎
-
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 ↩︎
-
Lapsed listener problem - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Lapsed_listener_problem ↩︎
-
Observer · Design Patterns Revisited - Game Programming Patterns, 访问时间为 十月 27, 2025, https://gameprogrammingpatterns.com/observer.html ↩︎
-
Microsoft System Center: Operations Manager Field Experience, 访问时间为 十月 27, 2025, https://download.microsoft.com/download/8/2/8/828C05A2-E6A0-436A-9AE1-704A8005E355/9780735695825.pdf ↩︎ ↩︎
-
Strategy in PHP / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/php/example ↩︎
-
Strategy in Java / Design Patterns - Refactoring.Guru, 访问时间为 十月 27, 2025, https://refactoring.guru/design-patterns/strategy/java/example ↩︎