开篇:为什么我们需要设计模式?
开篇:为什么我们需要设计模式?
你有没有过这样的经历——打开衣柜,发现里面乱七八糟,袜子塞在裤子里,毛衣压在T恤上,找一件东西要翻半天?于是你花了一个周末,把衣柜重新整理:左边挂衬衫,右边挂裤子,袜子放在专门的小格子里,内衣归到抽屉里。整理完之后,你会发现:找衣服快了,心里也清爽了,甚至连"今天穿什么"这件事都不再让人焦虑。
写代码这件事,和整理衣柜惊人地相似。一个项目刚开始的时候,代码量小,怎么写都能跑;可随着功能越来越多,逻辑越来越复杂,如果没有一套"整理思路",代码就会慢慢变成那堆乱糟糟的衣柜——找一个功能要翻几十个文件,改一个地方会莫名其妙地影响另一个地方,新人接手项目时恨不得把键盘摔了。
设计模式,就是前辈程序员留给我们的"衣柜整理手册"。它不是什么高深的理论,也不是大牛们的专利,而是一套被反复验证、行之有效的"代码整理套路"。这一章,我们不谈具体的模式,不写一行代码,只聊一件事:设计模式到底是什么,我们为什么必须学习它。
---
一、先别被名字吓到:设计模式就是"经验的打包"
很多人第一次听到"设计模式"这四个字,脑子里浮现的画面是这样的:一群穿着高领毛衣的技术专家,端着咖啡,在白板前画UML图,嘴里蹦出"单例"、"工厂"、"观察者"这些谁也听不懂的词。好像设计模式是某种只有天才才能掌握的神秘咒语,普通人只能敬而远之。
这是一种典型的误解,而且误解得相当深。
让我用几个生活中的例子,把这层神秘感一点点剥掉。
例子一:宜家家具 vs 自己打家具
假设你要在家里装一个衣柜。你有两个选择:第一,去宜家买一套标准化的衣柜,回家按照说明书拼装;第二,自己去木材市场买板子,自己量、锯、钉、漆,做一个独一无二的衣柜。
哪个更便宜?乍一看,自己打好像更省钱,材料成本低嘛。但算上时间成本、工具成本、做坏了重来的成本,再考虑做出来的质量,你大概率会发现:买宜家的更划算。
为什么?因为宜家的设计师已经替你解决了一堆问题——板材多厚才不容易变形?螺丝孔怎么钻才不容易滑丝?背板怎么固定才不留缝隙?这些细节,如果你没有经验,靠自己摸索,可能要失败好几次才能搞明白。
宜家的解决方案,本质上就是把"装家具的经验"打包成了一个标准化产品。你不用懂木匠手艺,也能装出一个像样的衣柜。
设计模式干的就是这件事——它把"写代码的经验"打包成了一套套标准化的解决方案模板。你不需要每次都从零开始思考"这个问题怎么解决",而是看看前辈们有没有留下"说明书",照着做就行。
例子二:菜谱和做菜
再举一个更接地气的例子:做菜。
红烧肉怎么做好吃?糖醋排骨要什么时候放醋?西红柿炒蛋是先炒蛋还是先炒蛋液?这些问题,厨师学校的老师傅们早就研究透了,写成了一本本菜谱。菜谱就是"做菜的模板"——它告诉你火候多大、调料多少、步骤先后。你照着菜谱做,七八成的概率能做出一道像样的菜。
但菜谱不是法律条文。你完全可以根据自己的口味调整:少放点糖、多加点辣、最后撒点葱花。菜谱的本质,是"经验的可复用形式"。
设计模式也是一样。它不是"必须严格遵守的规则",而是"经验的可复用模板"。你理解它的精髓之后,完全可以根据自己的项目情况灵活调整——但前提是,你得先懂"为什么这样做"。
例子三:装修房子的"套路"
我自己装修过房子,第一次装修的时候走了不少弯路。比如:插座装少了,后来到处拉插线板;卫生间没做干湿分离,洗澡的时候整个浴室都是水;柜子做到顶才不积灰,但我当时没做,结果柜子顶上堆了一年的灰。
第二次装修,我就学聪明了:插座多预留、卫生间一定干湿分离、柜子必须到顶、厨房台面高度要根据主厨身高定。这些"套路",是我用真金白银换来的经验教训。
软件行业也有完全相同的"装修经验"。比如:对象之间的依赖关系要尽量松散,不要一个类死死依赖另一个类的实现细节;创建对象的逻辑要集中管理,不要到处散落 `new`;状态变化的时候要通知所有关心它的人,不要让调用者自己去轮询……
这些经验,被前辈程序员们总结成了一条条"模式",写进了书里,传给了后人。你学会了,就相当于"装过一次房子",以后装修会顺畅很多;不学,就只能像我第一次那样,用真金白银交学费。
设计模式的本质定义
到这里,我们可以给设计模式下一个比较准确的定义了:
> 设计模式(Design Pattern)是在特定情境下,针对反复出现的问题,被反复验证有效的解决方案模板。它不是具体的代码,而是一种"思路"——告诉你"在这种情况下,可以这样组织代码"。
注意这个定义里的几个关键词:
- **"特定情境"**:模式不是万能的。每种模式都有自己的适用场景,也有不适用的时候。我们后面会反复强调这一点。
- **"反复出现的问题"**:模式解决的问题不是一次性的奇思妙想,而是几乎每个项目都会遇到的共性问题。
- **"反复验证有效"**:模式之所以成为模式,是因为它被无数的程序员在无数的项目里验证过,确实好用。
- **"解决方案模板"**:模式提供的是思路和结构,不是可以直接复制粘贴的代码。
把这四点综合起来,你就抓住了设计模式的本质:它不是金科玉律,而是经验模板。
---
二、三十年的沉淀:设计模式是怎么来的?
了解了设计模式是什么,我们再聊聊它是怎么来的。知道一段历史,能帮你更好地理解一个概念——就像了解一个人童年的经历,能帮你理解他成年后的性格。
软件危机与面向对象的兴起
设计模式诞生于 1990 年代,但它的根,要追溯到更早。
1960、70 年代,软件行业经历了一场著名的"软件危机"(Software Crisis)。那是一个硬件越来越强、需求越来越复杂的年代,但软件开发的方法还停留在"想到哪写到哪"的阶段。结果就是:项目越做越大,代码越来越乱,Bug 越来越多,最后整个项目崩溃——这种现象在当时有一个形象的名字,叫"意大利面条代码"(Spaghetti Code),意思是代码像一盘意大利面条一样缠在一起,理不清头绪。
1968 年,荷兰人 Edsger Dijkstra 写了一篇著名的论文《Go To Statement Considered Harmful》(Goto 语句有害论),呼吁程序员不要再滥用 goto 跳转,要用结构化的控制流(if、while、for)来组织代码。这是软件工程史上的一个重要转折点。
到了 1980 年代,面向对象编程(OOP)开始流行。C++、Smalltalk 这些语言把"对象"和"类"的概念引入了主流编程。和过程式编程相比,面向对象提供了封装、继承、多态这些强大的武器,让程序员可以更好地组织代码。很快,软件行业迎来了一波"OO 革命"。
但新问题来了:面向对象的威力是有了,但很多人不知道该怎么用。封装、继承、多态就像一把好刀——刀是好刀,但不会用的人,要么切不到东西,要么切到自己。
于是,程序员们开始讨论:"遇到这种情况,OO 该怎么设计?""那种情况下,哪个对象该负责什么?" 大家聚在一起交流经验,发现虽然项目千差万别,但很多问题的解法是相通的。
建筑学的启发
有意思的是,软件行业的这场讨论,受到了建筑学的直接启发。
建筑学里有"建筑模式"(Architectural Pattern)的概念。一个有经验的建筑师,看到你要建一所学校,立刻就能在脑子里调出"学校"的成熟方案:教室怎么排列、走廊多宽、楼梯放在哪、操场多大、窗户朝哪开。这些不是建筑师临场发挥,而是经过几百年的实践沉淀下来的"套路"。
1977 年,建筑师 Christopher Alexander 写了一本神书《A Pattern Language: Towns, Buildings, Construction》(《模式语言:城镇、建筑、构造》)。这本书提出了一个革命性的观点:好的设计不是设计师的个人灵感,而是可以被描述、被学习、被传承的"模式语言"。Alexander 列出了 253 个建筑领域的模式,每个模式都有统一的格式:名字、上下文、问题、解决方案。
软件行业的人读到这本书,如获至宝。他们意识到:建筑可以有模式,软件也可以有啊!
Gang of Four 与 1994 年的奠基之作
时间来到 1990 年代。四位软件工程师——Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides——开始系统地整理软件设计领域的模式。他们四个人的名字首字母连起来,被戏称为"Gang of Four"(四人帮),简称 GoF。
1994 年,他们出版了那本注定要载入软件工程史册的书:《Design Patterns: Elements of Reusable Object-Oriented Software》(《设计模式:可复用面向对象软件的基础》)。
这本书一共整理了 23 个经典的面向对象设计模式,分为三大类:
- **创建型模式(Creational Patterns)**:5 个,解决"对象怎么创建"的问题。
- **结构型模式(Structural Patterns)**:7 个,解决"类和对象怎么组合"的问题。
- **行为型模式(Behavioral Patterns)**:11 个,解决"对象之间怎么协作、职责怎么分配"的问题。
这 23 个模式,成了后来所有设计模式讨论的"原点"。不管你后来学了多少花里胡哨的新模式,万变不离其宗,基本都是这 23 个的衍生或变体。
这本书的影响有多大?我给你几个数据感受一下:
- 它是计算机领域被引用次数最多的书籍之一。
- 它被翻译成了几十种语言。
- 它几乎成了所有软件工程师的"必读书目",哪怕你不是面向对象程序员,也能从中学到设计的智慧。
- 时至今日,30 年过去了,这本书的核心思想依然没有被超越。
模式的"四要素"
GoF 在书里给每个模式都规定了统一的描述格式,包含四个要素:
1. 模式名称(Pattern Name):一个精炼的、便于交流的名字,比如"单例"、"工厂"、"观察者"。 2. 问题(Problem):在什么情境下会遇到这个问题?问题有哪些约束和征兆? 3. 解决方案(Solution):用什么结构、用什么机制来解决这个问题?不需要给出完整代码,而是给出抽象的描述。 4. 效果(Consequences):用这个模式有什么好处和代价?有没有适用范围的限制?
这个四要素格式特别重要,因为它告诉了我们学习模式的正确姿势:不能光记名字和代码,要理解"问题-解决方案-效果"这个完整的故事。这也是我们这本书要贯彻的方法论。
从 1994 到今天:模式的进化
GoF 的 23 个模式是奠基之作,但绝不是终点。1994 年以后,软件行业又发生了翻天覆地的变化:
- 互联网崛起,分布式系统成为主流,催生了大量的并发模式、分布式模式。
- 函数式编程复兴,一些传统 OO 模式的用武之地变小了,或者有了新的写法。
- 框架的兴起让很多模式被"内化"——比如 Spring 框架把工厂模式、代理模式、单例模式都封装了,你甚至不用写这些模式也能用上。
- 测试驱动开发(TDD)、领域驱动设计(DDD)、微服务架构……每一种新方法论都带来了一些新的"模式化思路"。
所以我们这本书会在 GoF 经典 23 模式的基础上,适当扩展到 30 多个模式,并且加入现代软件工程的视角。但万变不离其宗,经典的永远是经典的,先把 23 个经典吃透,再看其他的就是水到渠成。
---
三、三个无法拒绝的理由:设计模式到底有什么用?
知道了设计模式的本质和历史,你可能还会问:它到底能给我带来什么实实在在的好处?
我可以给你列出十几条好处,但没必要。我只讲三个最核心、最不可替代的价值——这三个价值,是我这么多年代码生涯里感受最深的,也是我认为你即使不想成为架构师,也应该学设计模式的根本原因。
价值一:复用前人的经验,少走弯路
第一个价值:复用经验。
软件行业有一句广为流传的笑话:"不要重复发明轮子"(Don't reinvent the wheel)。这句话的意思是:别人已经解决过的问题,你没必要从头再摸索一遍。设计模式,就是"已经被发明过的轮子"的目录。
我自己刚入行的时候,写过一个文件上传的功能。当时我设计了一个巨复杂的类,把文件校验、上传到本地、上传到云端、进度通知、错误处理……所有逻辑全塞在一个类里。结果这个类膨胀到了 2000 多行,维护起来痛不欲生——改一个校验规则,要把整个文件翻一遍;想换一种云存储服务,要把所有上传逻辑重写一遍。
后来我学了"单一职责原则"和"策略模式",才明白:当一个类的功能太多时,应该拆开;当有多种算法或行为需要切换时,应该用策略模式封装。我把那个 2000 行的类拆成了 7 个小类,加了一个策略模式,以后切换云存储只需要改一个配置文件,新加一种存储方式只需要写一个新的策略类,完美。
这就是复用经验的价值。一个有经验的程序员,可能要花几个月才能悟出"原来这里应该用策略模式";你通过学习设计模式,可能几天就能 get 到这个点。这中间的差距,就是经验的价值。
更妙的是,设计模式里沉淀的不仅是"怎么做",还有"为什么这么做"、"不这么做会怎样"。这些背后的思考过程,比结论本身更有价值。
价值二:让团队沟通有共同语言
第二个价值:沟通效率。
你有没有经历过这样的对话:
> 小 A:我觉得这个地方的代码应该重构一下,把创建逻辑单独提出来。 > 小 B:嗯,提到一个方法里就行了吧? > 小 C:我觉得应该提到一个独立的类里。 > 小 A:那不就变成工厂了吗? > 小 B:什么工厂?工厂是干啥的?
这段对话为什么效率低?因为三个人脑子里对"工厂"这个概念没有共识。如果团队里大家都学过设计模式,这段对话会变成:
> 小 A:我觉得这里应该用工厂模式,把对象的创建逻辑集中起来。 > 小 B:同意。具体用简单工厂还是工厂方法? > 小 C:我觉得工厂方法更合适,因为我们以后可能扩展。 > 小 A:行,那就工厂方法。
是不是效率完全不一样?设计模式给程序员提供了一套"行业黑话",让沟通变得极其高效。
这种价值在大型团队里尤其明显。我曾经在一家公司做架构师,团队有 50 多个开发人员,分成十几个小组。每个小组负责不同的模块。最初,我们的设计评审会非常痛苦——每个人对"好代码"的理解都不一样,讨论起来鸡同鸭讲。后来我们强制要求所有架构文档必须用"模式名 + 类名 + 职责"的方式来描述,比如"使用观察者模式,EventBus 类负责通知所有监听者"。
神奇的事情发生了:会议效率提升了一倍,文档质量提升了三倍,新人上手时间从两个月缩短到两周。设计模式就像建筑行业的"图纸符号",让所有人能用同一种语言描述复杂的设计。
这个价值往往是新手最容易忽略的,但等你真正进入一个团队,就会发现它的分量有多重。
价值三:让代码能优雅地应对变化
第三个价值:应对变化。
软件行业有一句老话:"唯一不变的就是变化本身。"需求会变、技术会变、人员会变、老板的想法会变……一个项目从立项到上线,需求变更是常态。
很多新手程序员写代码的时候,只考虑"现在能跑就行"。但有经验的程序员会多想一步:"如果以后需求变了,我改起来麻烦不麻烦?"
设计模式的核心价值,就是让你的代码在变化面前保持优雅。
举个真实例子。我之前做过一个电商系统,最初只有"普通用户"和"VIP 用户"两种身份,定价逻辑很简单:
```java if (user.isVip()) { return price * 0.8; } else { return price; } ```
上线后,产品经理说:"我们要加企业用户,企业用户打 7 折。" 我改了一下,加了一个 else if。
又过了一阵,产品经理说:"VIP 用户还要分等级,不同等级折扣不一样。"我又改了一下。
再后来:"双 11 期间所有用户都打 9 折,但是 VIP 折扣要在 9 折基础上再打 8 折……"
每改一次,我都在那个 if-else 里加一个分支。改到第五次的时候,那个方法已经有 100 多行了,我自己都看晕了,新人接手的时候更是崩溃。
这就是典型的"没有应对变化能力"的代码。如果当时学了策略模式,我会把每种定价策略封装成独立的类,加新的折扣只需要加一个新的策略类,不用改原有代码。这叫"对扩展开放,对修改关闭"——这是设计模式的一条核心原则。
软件工程里有一个著名的"开放-封闭原则"(Open-Closed Principle),简单说就是:好的代码,应该让你加新功能的时候不需要改老代码。设计模式的所有套路,归根结底都是为了实现这个目标。
再举一个例子。我之前做消息推送系统,最初只支持邮件推送。后来要加短信推送,再后来要加 App 推送,再后来要加微信推送……
如果按照最直接的写法,我会写一堆 if-else:
```java if (type == "email") { sendEmail(...); } else if (type == "sms") { sendSms(...); } else if (type == "app") { sendAppPush(...); } ```
每加一种推送方式,都要改这个方法。改得多了,Bug 就多,而且测试起来非常麻烦。
但如果用了"观察者模式"或者"策略模式",我可以让每种推送方式自己注册自己,主体逻辑不用动。新加一种推送方式,只需要写一个新的类,注册一下就行。这就是设计模式应对变化的价值。
这三个价值——复用经验、提升沟通、应对变化——是我认为你学习设计模式最根本的三个理由。其他的好处,比如"代码更优雅"、"显得更专业"、"面试加分"等等,都是附带收益。
---
四、什么时候不该用模式:警惕"过度设计"的陷阱
讲了这么多设计模式的好处,我必须停下来泼一盆冷水:设计模式不是越多越好,不是什么时候都该用模式。
这是我见过的最普遍、也最危险的误区之一。
什么是过度设计?
我给你讲个故事。
我有个朋友,技术能力很强,设计模式背得滚瓜烂熟。有一次他接了一个小项目——一个公司内部的图书借阅系统,需求很简单:员工可以借书、还书、查看自己的借阅记录。后端用 Spring Boot,前端用 Vue,总共也就 30 多个接口。
按理说,这么小的项目,用最朴素的 MVC 架构就够了。但他觉得"不能浪费了我的设计模式知识",硬是塞了一堆模式进去:
- 创建对象用工厂模式。
- 数据访问用 Repository 模式。
- 业务逻辑用策略模式。
- 跨模块通信用观察者模式。
- 配置管理用单例模式。
- 业务编排用责任链模式。
- ……
结果呢?项目从原计划的 2 周拖到了 2 个月,代码量从预估的 5000 行膨胀到了 2 万行,Bug 一大堆,因为模式套模式,新人