书名:Agent设计模式 图解可复用智能体架构
ISBN:978-7-115-69047-0
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 黄 佳
责任编辑 秦 健
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书是Agent时代的架构设计与工程实践指南,旨在连接传统软件工程与大模型应用。全书分为上下两篇:上篇以历史演进为线索,剖析了软件范式从“确定性结构”向“概率性智能”的演进,揭示Agent诞生的必然性;下篇作为实战手册,系统构建了涵盖感知、记忆、推理、行动、反思与协作的通用架构,并拆解了21个核心设计模式。本书超越代码层面,从架构、机制与实践等多维度出发,通过丰富案例,为解决“如何在不确定的大模型上构建可靠系统”提供了可复用的方法论。
本书适合对Agent设计、软件工程、人工智能等领域感兴趣的读者,包括相关专业的师生、研究人员、工程师、架构师、产品经理及运营人员等阅读。
这本书系统地讲解了如何让Agent稳定、可靠地运行:从感知、记忆,到反思与协作,每个核心设计模式都配套提供了即插即用的Agent工程模块。我推荐有志于Agent研发的读者阅读和学习。
——周明 澜舟科技创始人兼CEO
Agent正在重构AI时代的应用范式。在Agent出现之前,AI应用的本质是将人类的智能嵌入合适的人机交互流程中;而Agent的本质,则是将AI自身的智能融入新型的人机交互方式之中。在构建Agent的过程中,我们的工程方法本身也在被重新塑造。
这本书出色地拆解了构建Agent的“道”与“术”,既有思想深度,又具有工程实践价值,值得对Agent感兴趣的读者仔细研读。
——李建忠 奇点智能研究院院长,CSDN高级副总裁
所有模型都是不准确的,但有些模型是有用的。期待黄佳的这本书,能在百花齐放的Agent时代,帮助我们识别并掌握那些真正有效的模式。
——邹欣 北京中关村学院智能创新中心负责人
这本书详细介绍了Agent的设计方法与实现手段,为人工任务的智能化转型开启了无限可能。
——吴飞 浙江大学本科生院院长
这本书以工程化视角深入探讨了“Agent如何被设计”这一核心问题,将感知、记忆、推理等认知模块转化为具有明确接口的设计范式,为智能系统从理论模型迈向稳健落地提供了关键的共通范式与设计语言。
——马少平 清华大学计算机科学与技术系教授,《艾博士:深入浅出人工智能》作者
这本书为构建Agent系统提供了深入且实用的指导。书中系统地介绍了21个核心设计模式,涵盖从任务规划到多Agent协作的全方位方法,助力开发者在复杂环境中打造高效、灵活的Agent。作者结合丰富的实例与扎实的理论分析,清晰勾勒出智能软件工程的未来走向。
正如《软件工程3.0:大模型驱动的研发新范式》所强调的,软件开发正加速迈向智能化的新阶段。这本书恰如其分地将这一理念融入具体的设计实践,是大模型应用开发者在快速演进的技术浪潮中不可或缺的参考指南。
——朱少民 同济大学特聘教授,《软件工程3.0:大模型驱动的研发新范式》作者
许多AI项目止步于演示阶段,问题往往不在于模型本身,而在于系统设计。黄佳的这本书从真实工程视角出发,提出了让Agent稳定运行、协同扩展的设计方法,对AI初创公司以及成熟企业推进Agent的企业级落地,具有极高参考价值。
——毛华 腾讯云语音云前总经理、ccMonet.AI创始人兼CEO
在AI时代,Agent从被动工具演变为智能伙伴,掌握Agent设计模式,解锁智能系统开发的新范式。
——Datawhale AI开源学习社区
“Agent(智能体)并非一种新的模型,而是一个全新的物种——它使软件首次拥有了意图与时间的概念。”
——黄佳
我们并非首次发明智能系统,而是正在再次发明它:
让系统从被动的代码,转变为能感知、会反思、可协作的行动者。
软件工程自诞生以来,便始终在与复杂性角力:我们用结构化编程驯服流程,用面向对象编程管理变化,用微服务拆解耦合。然而,时至今日,复杂性不再仅源于规模,更源于不确定性。
■ 任务目标是开放的,需求在对话中持续产生。
■ 环境状态是动态的,信息持续从外部世界涌入。
■ 决策过程是概率性的,路径需要经过权衡与反思。
■ 输出作用于世界,而世界又反过来重塑输入。
这迫使我们承认:传统软件工程的“输入—处理—输出”范式已然不足。系统不再只是被动执行的“程序”,而必须成为在真实世界中主动感知、推理与求解的Agent。
再发明的意义:我们需要为这种“主动性”构建工程化的结构与可复用的语言——正如当年GoF(设计模式的4位先驱)之于面向对象编程那样,我们也为Agent时代提供一套公共的,且可交流、可组合、可演进的设计模式。
设计模式的价值不在于“模板代码”,而在于可复用的思考路径。在AI时代,这一价值被重新点亮,至少有以下5个原因。
■ 从代码复用到决策复用。传统设计模式复用的是“类与关系”,而Agent设计模式复用的是感知方式、推理逻辑与行动选择策略。设计模式使我们在面对不确定性任务时,仍能获得可预期、可信赖的行为表现。
■ 从静态结构到动态闭环。现代AI系统形成了一个闭环:感知→记忆→推理→决策→行动→反馈→反思→再次行动。设计模式不再仅描述静态的组件依赖,而需要刻画循环机制、触发条件、阈值设定与护栏逻辑。
■ 从局部实现到跨边界协作。Agent需要与外部工具、服务,乃至其他Agent协同工作。设计模式使我们能够标准化地进行意图表达、协议交互与记忆共享,将“系统边界”转化为可设计、可组合的模块。
■ 从代码可读到行为可解释。AI系统的正确性不仅在于“跑通”,更在于“说得清”:为何如此路由?为何给出这个答案?设计模式天然内嵌可解释的“意图—结构—因果”链条,使AI系统行为具备透明性与可追溯性。
■ 从工程实践到组织对齐。当AI系统具备自治能力时,组织就必须与之建立新的分工机制与协同契约。设计模式成为团队间的通用语言,将“智能如何行动”转化为可沟通、可治理、可演进的共同约定。
在AI时代,设计模式再次成为“基础设施”。
若没有设计模式,我们将在每个项目中重新发明决策、记忆、反思与协作机制;而有了设计模式,我们便能将宝贵的时间聚焦于真正创造差异化价值的核心任务。
面向对象编程让我们将世界抽象为“对象”——对象拥有状态与方法,却没有目标,它只能等待被调用。
Agent则截然不同:它带着目标进入世界,会主动感知环境、检索记忆、推理规划、选择行动、吸收反馈,并动态调整策略。由此,系统边界发生了3层根本性迁移。
■ 生命周期的迁移:从“被动调用”转向“持续运行”。
■ 接口的迁移:从“方法签名”演进为“意图协议”。
■ 状态的迁移:从“内存中的局部状态”扩展为“嵌入世界的状态”(涵盖环境、其他Agent,以及时间维度)。
这意味着,我们必须以能动性视角重新构建系统架构。
■ 将“输入”重构为感知。
■ 将“中间态”治理为记忆与上下文。
■ 将“算法调用”升级为推理与规划。
■ 将“函数执行”转化为行动与协作。
■ 将“日志”提升为反思与自我改进。
面向对象编程依然有效,但它已成为一种“子语言”;而Agent设计模式才是这个时代的“母语”。
当系统跨越代码边界、步入真实世界时,被动的对象必须让位于主动的行动者。
协作曾是模块之间的调用关系。
在Agent时代,协作已成为主体之间的理解关系。
■ Agent与Agent:涵盖任务分解、角色分工、博弈与协商。
■ 人类与Agent:涵盖人类介入、共创共治、价值对齐。
■ Agent与世界:涵盖环境感知、空间行动、长期适应。
当协作规模足够庞大、反馈足够丰富时,系统将产生涌现——那不是被预先编写的功能,而是从持续互动中自然生长出的秩序。
在这一过程中,我们将“协作”升维为“共生”:系统与人类共处同一反馈循环,彼此成就——人提供价值锚点,Agent进行能力放大;人划定伦理边界,Agent拓展探索半径。
当协作进化为共生之后,软件设计将自然发生以下范式跃迁。
■ 设计对象:从“静态结构”转向“动态闭环”。
■ 质量目标:从“性能指标”转向“行为稳定性与可解释性”。
■ 工程接口:从单一的API(Application Programming Interface,应用程序接口)扩展为融合“意图、记忆、反馈与护栏”的多维交互系统。
■ 组织关系:从“提供交付物”演进为“人机共治、持续协同”。
本书并非一本“代码大全”,而是更关注如何让Agent在真实世界中稳定地做好事。
因此,全书采用统一的模式写作模板:
结构图(Structure)|核心机制(Motivation)|工程实现(Implementation)|演化与组合(Variants)|哲学启示(Insight)。
这种写法蕴含三重用意。
■ 让架构与行为一眼可读。
■ 让实现与替代方案可比、可选。
■ 让工程决策背后的思想可讨论、可继承。
全书主要内容分为上下两篇。
上篇 智能设计的哲学(第1章至第3章) 作为全书的思想地基,系统地回答以下核心问题。
■ 什么是Agent范式?
■ 为何必须构建闭环?
■ 模式如何成为心智语言?
■ 人与Agent如何共处共生?
可将上篇视为“认知工程学”的实践探索:系统地梳理从面向对象编程到Agent范式的演进逻辑,并从认知科学与控制论中汲取理论支撑,阐明Agent的本体结构与能动性根基。
下篇 智能设计模式(第4章至第9章) 围绕六大主轴——感知、记忆、推理、行动、反思、协作,构建从个体智能到社会协作的完整闭环。
在下篇中,每章既提供可复用的“工程部件”(即具体、可落地的设计模式),也锚定必要的“思想坐标”(阐释其背后的认知逻辑与系统哲学)。读者既可按需查阅、灵活取用,亦可按顺序阅读,逐步构建完整的Agent设计心智模型。
为便于实践落地,我提出了5条贯串全书的Agent设计原则。
■ 目标优先:一切从“要达成什么”出发,而非先考虑“能调用什么”。
■ 上下文为王:Prompt(提示)只是冰山一角,真正决定行为的是上下文工程与记忆治理。
■ 显式反馈:将反馈、反思与改进视为核心能力,内建于系统设计之中,而非上线后的补救或“善后”。
■ 渐进自治:让系统在安全护栏内逐步提升自治能力,始终保留人类介入的中止权与解释权。
■ 对齐与护栏:将伦理、安全与合规要求深度嵌入结构与协议,绝不把风险外包给“运气”或事后干预。
这些设计原则可以帮助我们在Agent时代日益加剧的不确定性中,锚定可预期、可治理、可演进的结果。
本书面向以下3类读者。
■ 工程师与架构师:将获得一套组织复杂智能系统的“可复用部件库”。
■ 产品经理与运营人员:将掌握与工程团队高效协作的“共通语言”。
■ 研究人员与教育工作者:将看到认知科学、控制论等理论如何在工程实践中“落地为形”,并转化为可操作的设计范式。
如果说:
过去百年,我们在“让机器会做事”;
过去十年,我们在“让机器会说话”;
那么未来十年,我们的核心命题将是“让机器会处世”。
这需要一套新语言,来描述系统的意图、界面与边界——而设计模式,正是这套语言。
我们相信:
当模式成为共识,Agent才可能成为基础设施;
当闭环被工程化,智能才可能迈向可信与可控;
当协作被制度化,AI才能与人类实现深度共生。
这本书,是一场集体的“再发明”。
感谢你加入这场“再发明”!
在一个寻常的夜晚,咖哥忽然意识到,在漫长的学习与思考过程中,自己很少被某个孤立的知识点所吸引。
真正令他兴奋的,是那些原本纷繁复杂、彼此纠缠的信息,在脑海中逐渐显露出清晰结构的那一刻——仿佛整个世界都被重新整理过。

他逐渐明白,真正驱动自己的并非知识本身,而是隐藏在知识背后的模式。
他关注的从来不是内容本身,而是内容如何被组织与整合,并构建成一个可理解的整体。
比起记住结论,他更在意系统为何以这样的方式被设计。
比起答案本身,他更执着于支撑答案成立的结构前提。
随着对模式的持续关注,一种稳定而清晰的能力结构逐渐浮现。
■ 模式发现(Pattern Discovery):从纷繁复杂的现象中提炼出共性结构的能力。
■ 模式生成(Pattern Generation):在既有理解的基础上,自主构建新框架与表达语言的能力。
■ 模式转译(Pattern Translation):将抽象结构以不同语言和视角清晰传达的能力。
这3种能力彼此交织,共同构成了一种独特的思维取向——以结构为中心、以系统为边界的思考方式。
他开始意识到,自己对模式的敏感并非一时兴起,而是一种长期养成的认知习惯。
在他的世界里,复杂并不可怕,混乱并非终点。
一切看似无序的事物,终将以某种方式显露出内在的秩序。
也许,下一步该做的事,已然不言自明……

如果把软件工程看作人类理性的延伸,那么它的历史其实是一部复杂性管理史。
每一次范式变革,都是我们面对复杂性的恐惧与超越。
法律的制定,往往不是出于对完美的追求,而是出于对混乱的恐惧。
在GoF确立软件工程的“秩序”之前,软件设计和工程的世界曾经历过一段野蛮生长的岁月——那是一个充满自由,却也充满风险的“蛮荒时代”。
1994年,4位工程师(Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)[1]出版了一本软件工程史上的殿堂级作品——《设计模式:可复用面向对象软件的基础》。
[1] 这4位作者常被人们称为“四人组”(Gang of Four,GoF)。
在理解这部作品的意义之前,我们必须回到那个时代的语境。
20世纪90年代初的软件世界,正经历着一场静悄悄的危机。面向对象编程从学术界的“玩具”变成了工业界的主流;C++正在征服世界,Smalltalk的优雅令人着迷,而Java即将诞生。开发者们第一次拥有了“对象”这个强大的抽象工具,却发现自己像拿着利剑的孩子——有了武器,却不知如何优雅地战斗。
代码在膨胀。一个简单的文本编辑器可能包含上万行代码,一个企业系统动辄包含数十万行代码。复杂性不再呈线性增长,而是呈指数级“爆炸”。每增加一项功能,都可能引发整个系统的震荡;每修复一个bug,都可能制造3个新的问题。
“为面向对象设计中反复出现的问题提供一种可复用的解决方案。”这是GoF的使命宣言,但设计模式的野心远不止于此。
“模式”这个概念并不是GoF发明的。他们的灵感来自建筑师克里斯托弗·亚历山大(Christopher Alexander)的《建筑模式语言》。亚历山大相信,优秀的建筑不是天才的灵光一现,而是建筑师对“永恒形式”的发现和组合:一个舒适的窗台、一条蜿蜒的小径、一个围合的庭院——这些模式在千年的建筑史中反复出现,因为它们响应了人类深层次的需求。
GoF注意到软件与建筑之间的相似性:二者都是在约束中进行创造,都需要平衡功能与美感,也都面临着复杂性的挑战。于是,他们开始收集、整理并提炼那些在优秀软件中反复出现的“形式”。
最终,23个设计模式被精心挑选出来,并划分为三大类型——创建型模式、结构型模式和行为型模式。GoF提出的这23个设计模式共同构成了一套系统化的设计世界观(见图1-1)。

图1-1 一套系统化的设计世界观
这23个设计模式代表了软件中的“对象”从“出生”到“成长”,再到“社交”的发展轨迹。在这里,GoF试图规划“对象”在其“一生”中所有可能遇到的场景。
创建型模式聚焦于对象的诞生。表1-1列出了创建型模式所包含的设计模式及其核心目的、工程视角的解释及类比。
表1-1 创建型模式
| 设计模式 |
核心目的 |
工程视角的解释 |
类比 |
|---|---|---|---|
| 单例(Singleton) |
保证某个类在系统中只有一个实例,并且全局都能访问到 |
通过私有构造函数+静态方法/属性控制实例数量。需要注意的是,并发环境下要考虑线程安全、懒汉/饿汉模式,以及测试时的可替换性。常用于共享资源(如配置、日志器、线程池等) |
像太阳系中的太阳:只能有一颗,其他星球都围着它转 |
| 工厂方法(Factory Method) |
统一、封装对象创建逻辑,让“使用者”不需要关心构造细节 |
通过工厂类/工厂方法集中创建对象:根据传入的参数或配置,决定创建哪一种类实例。优点是解耦“使用者”和“具体类”,便于后续扩展或替换实现 |
像汽车生产线:客户只负责选择“车型”,而不关心焊接、喷漆等技术细节 |
| 建造者(Builder) |
分步骤构建复杂对象,支持灵活配置和“可读性强”的构建过程 |
通过“建造者”逐步设置属性,最后统一“建造出”成品。优点是容易控制“构建顺序”和校验逻辑。常用于对象有很多可选参数或组件且构建过程相对稳定的场景 |
像搭积木:首先搭建底座,其次搭建墙体,最后放置屋顶,每一步都清晰可控 |
| 原型(Prototype) |
通过“克隆已有对象”来创建新对象,而不是每次从零开始构建 |
当对象构建开销大(如涉及深层次配置、复杂初始化)时,可通过“克隆”一个已有原型,再进行少量修改即可。需要注意的是,应设计好浅拷贝/深拷贝,避免因共享或复制不当导致bug |
像细胞分裂:先复制一份,再在复制体上进行变化 |
| 抽象工厂(Abstract Factory) |
生产“产品族”,保持同一系列产品间的一致性与兼容性 |
针对一整套相关产品(如Button、TextBox、Menu,都有Windows风格或macOS风格可选),抽象出一个工厂接口,再为不同风格实现具体工厂。需要注意的是,客户端只依赖抽象工厂,从而保证全局风格统一、易于切换 |
像主题套装:购买“北欧风家具套装”,沙发、茶几、柜子风格统一 |
结构型模式聚焦于对象的组合。表1-2列出了结构型模式所包含的设计模式及其核心目的、工程视角的解释及类比。
表1-2 结构型模式
| 设计模式 |
核心目的 |
工程视角的解释 |
类比 |
|---|---|---|---|
| 适配器(Adapter) |
让原本接口不兼容的两个组件可以一起 |
通过一个“中间层”对象,把旧接口转换成新接口(或反过来)。常用于接入第三方库、迁移旧系统、统一接口规范等场景。对外(新接口)不变,对内完成协议适配 |
像电源转换器:插头不一样,但通过转换头就能连接电源 |
| 桥接(Bridge) |
分离“抽象部分”和“实现部分”,让两边可以独立扩展 |
把多维度的变化拆解开,如“图形类型(圆/ |
像遥控器和电视机:遥控器是一套抽象控制方式,不同品牌的电视机只要实现该协议,就能被同一个遥控器控制 |
| 组合(Composite) |
以“树形结构”统一处理单个对象与组合对象 |
定义统一的组件接口(如Component接口),使叶子节点和组合节点都实现该接口。此时,客户端可以忽略树形结构的复杂性,统一调用添加、移除或操作函数。常用于构建UI组件树、文件系统等 |
像文件系统:文件和文件夹在很多操作上是统一的(如复制、删除、移动) |
| 装饰器(Decorator) |
在不修改原始类的前提下,动态地给对象添加功能 |
通过包装的方式,装饰器实现与被装饰对象相同的接口,并在内部持有一个“原对象”引用,在调用前后增加逻辑。优点是可实现灵活的功能组合。需要注意的是,要避免多维度继承导致的类数量“爆炸”问题 |
像俄罗斯套娃:一层套一层,每一层都可以多一个“花纹/功能” |
| 外观(Facade) |
对复杂子系统提供一个“统一、简单”的入口 |
封装一组复杂接口调用逻辑,对外暴露一个简化的API(Application Programming Interface,应用程序接口)。例如,一个VideoConverter内部可能调用几十个小模块,但对外只需要提供convert(input, outputFormat)。常用于降低使用门槛、减少耦合 |
像汽车方向盘:驾驶者只需要操作方向盘和踏板,不需要直接操控发动机、变速箱 |
| 享元(Flyweight) |
通过“共享”细粒度对象,减少内存消耗 |
将对象的“内部不变状态”(如字体、颜色)抽出来共享,而“外部可变状态”(如坐标、大小)由调用方自行维护。常用于有大量相似对象的场景 |
像字母表:26个字母是共享的,但它们在不同单词、不同行内的位置各不相同 |
| 代理(Proxy) |
控制对某个对象的访问,在访问前后附加额外逻辑 |
代理类和被代理类实现相同的接口,外部“以为”自己在用真实对象,实则通过代理完成操作。优点是可实现懒加载、权限控制、远程访问、缓存等功能 |
像秘书:帮你挡访客、筛选电话,决定什么事需要直接打扰你 |
行为型模式则规定了对象如何协作。表1-3列出了行为型模式所包含的设计模式及其核心目的、工程视角的解释及类比。
表1-3 行为型模式
| 设计模式 |
核心目的 |
工程视角的解释 |
类比 |
|---|---|---|---|
| 责任链(Chain of Responsibility) |
把请求沿着一条链传递下去,每个节点决定“处理或继续传递” |
把多个处理者串成链,让请求发出方不需要关心谁来处理。常用于日志处理、权限审批流、HTTP中间件等。优点是利于解耦。需要注意的是,链过长会导致调试困难 |
像公文流转:按科员→经理→总监的顺序逐级审批 |
| 命令(Command) |
把“请求”封装成对象,便于排队、撤销、重做、日志记录 |
将一次操作抽象为Command对象(包含接收者、参数、执行逻辑),调用方只负责“发出命令”,而不直接操作接收者。常用于事务、操作记录、宏命令执行等 |
像遥控器按钮:每个按钮代表一个命令,按下就执行相应操作 |
| 迭代器(Iterator) |
提供统一方式,按顺序访问聚合对象内部元素,而不暴露其内部结构 |
通过first、next、hasNext等接口遍历集合,隐藏集合内部结构(如数组、链表或树结构)。优点是便于扩展不同遍历策略(如正向、反向、按层级遍历等) |
像翻书:只需要“翻到下一页”,不需要关心纸张是如何装 |
| 观察者(Observer) |
建立“一对多”的通知机制,状态变化时自动通知所有订阅者 |
主题对象维护观察者列表,当状态变更时,逐个回调。常用于事件系统、GUI监听、消息推送、中间件发布/订阅。需要注意的是,应避免通知风暴和环形依赖 |
像报纸订阅:杂志社一出新刊,所有订阅者都会收到 |
| 策略(Strategy) |
将可替换的算法封装起来,运行时按需 |
定义统一的策略接口,不同实现代表不同算法(如不同排序方式、不同缓存策略、不同计费方式)。需要注意的是,调用方只依赖接口,可以在配置或运行时切换 |
像选择出行方式:坐地铁、打车、骑车,本质都是“从地点A到地点B”,只是策略不同 |
| 模板方法(Template Method) |
在父类中定义“处理流程骨架”,子类只重写其中某些步骤 |
父类定义templateMethod流程,把可以变化的步骤抽象为钩子方法,由子类实现。常用于流程稳定、步骤可变的场景。缺点是对继承依赖较重 |
像食谱:流程是备菜→下锅→调味→出锅,不同菜品只在细节上有变化 |
| 访问者(Visitor) |
在不改变数据结构的前提下,为其增加新操作 |
分离“数据结构”和“对数据的各种操作”:数据结构提供accept(Visitor),具体“访问者”实现不同操作(如统计、导出、校验等)。常用于数据结构稳定但操作常变化的场景 |
像税务审计:企业结构不变,审计员带着不同“审计方案”来回查看 |
| 状态(State) |
允许对象在其内部状态发生改变时改变自身行为 |
将不同状态下的行为封装到独立的类(如StateA、StateB)中,并将上下文的行为委托给当前状态对象,从而消除庞大的if-else或switch-case状态判断语句 |
像心情变化:心情好时工作效率高,心情差时只想睡觉——人没变,但行为随着状态的改变而发生了 |
| 备忘录(Memento) |
在不破坏封装的前提下,捕获并恢复对象的内部状态 |
创建一个“快照对象”来保存状态,并将其交由管理者保管;原对象可随时从该快照恢复。常用于编辑器的“撤销/重做”功能和游戏存档等场景 |
像游戏存档:打Boss前先存档,失败后读取存档重新 |
| 中介者(Mediator) |
通过一个中介对象封装一系列对象之间的交互 |
将网状的“多对多”关系简化为星形的“一对多”关系:各对象之间不再直接通信,而是通过中介者进行消息转发。这降低了组件间的耦合度,但也可能导致中介者职责过重、结构臃肿 |
像机场塔台:飞机之间不直接通信,所有起降指令均由塔台统一调度 |
| 解释器(Interpreter) |
给定一种语言,定义其文法表示,并实现一个解释器来解析和处理该语言 |
将特定领域的问题建模为一种语言的语法规则(如SQL解析、正则表达式、数学公式计算等),并为文法中的每条规则创建对应的类。常用于语法相对简单且变动不频繁的场景 |
像乐谱演奏:乐谱是一种语言,演奏家是解释器,依照符号规则将音乐还原出来 |
审视这些在现代软件工程实践中仍广泛使用或已逐渐淡出的设计模式,我们会发现:即便对具备较强逻辑思维能力的软件工程师而言,这23个设计模式也相当抽象,有些甚至难以直观把握。
然而,GoF的伟大之处在于:他们首次将“经验”转化为“词汇”。
在此之前,当一名开发者想向另一名开发者解释自己的设计时,必须画图、编写伪代码、列举示例,费尽口舌却可能词不达意。而现在,他只需要说:“这里使用了观察者模式。”对方立刻心领神会。他再说:“这里我们使用工厂方法模式和策略模式就可以把不同支付方式的逻辑解耦。”如果对方也懂设计模式,双方便能够迅速在抽象层面达成共识,沟通成本大幅下降。
这里,设计模式不仅是代码写法,更是一种团队沟通的语言。
这种共同词汇的建立,意义不亚于巴别塔倒塌后人类重新找到共同语言。它让软件设计从“手工艺”走向“工程学”,使经验得以跨越时空传承。一名在硅谷的开发者和一名在东京的开发者,即使从未谋面,也能通过“工厂方法模式”“观察者模式”等词汇产生共鸣。
此时此刻,设计模式作为一种压缩算法,将冗长的技术细节压缩为高密度的文化符号(见图1-2),让开发者之间的沟通像光纤通信一样高效。


图1-2 设计模式作为一种压缩算法,将冗长的技术细节压缩为高密度的文化符号
设计模式的价值,在于其背后的工程思维:如何在复杂系统中降低不确定性,让对象各司其职、彼此独立、可随时替换。创建型、结构型、行为型这三大类型回答了“对象从哪里来、如何组合、如何协作”这3个经典问题。
理解这23个设计模式的核心,不在于“记住23个名字”,而在于掌握这三大类型所蕴含的思维方式。
■ 创建型思维:回答“对象从哪里来”,解决构建复杂、耦合和可扩展性问题。
■ 结构型思维:回答“对象如何组合”,解决系统模块之间的连接方式与复杂度问题。
■ 行为型思维:回答“对象如何协作”,解决职责划分、耦合度和可扩展的业务流程问题。
每个设计模式都在引导开发者围绕以下问题展开思考。
■ 现在到底卡在哪里?(创建复杂?类太多?依赖关系混乱?功能难以扩展?)
■ 用这个设计模式之后,谁与谁解耦了?(使用者与创建者解耦?抽象与实现解耦?数据与操作解耦?)
■ 将来要改动时,可以只改哪一小块?(只更换策略?只添加一个装饰器?只添加一个工厂方法?)
设计模式之间则可以组合使用,而非互斥。
■ “抽象工厂模式+组合模式+迭代器模式”可以构建一个跨平台UI组件库。
■ “命令模式+备忘录模式”可以实现强大的撤销/重做系统。
■ “观察者模式+策略模式+责任链模式”可以实现灵活可配置的事件处理系统。
当你真正掌握设计模式时,相当于掌握了一套“可复用的思考框架”。它让你在面对变化时不再依赖灵感,而是能像建筑师一样,把系统拆解为稳定的结构、清晰的职责和可控的演化路线。
设计模式不是技巧,而是让复杂系统保持长期可演进的工程哲学。
“软件可以被组织,就像城市可以被规划。”这是GoF传递的世界观,也是那个时代软件工程界的集体信念。人们相信,只要找到正确的设计模式,组合正确的结构,就能征服复杂性,建造完美的软件宫殿。
但是,秩序总是有代价的。
人们发现自己陷入了新的困境:初学者开始“为了设计模式而设计模式”,简单问题被复杂化,代码充斥着不必要的抽象层。“设计模式”从“解药”变成了“毒药”,从工具变成了教条。
一个简单的“Hello World”程序,在狂热的设计模式信徒手中,可能如下所示。
| Code |
// 通过工厂方法模式创建打印器 PrinterFactory factory = new PrinterFactory(); // 通过策略模式选择输出策略 OutputStrategy strategy = new ConsoleOutputStrategy(); // 通过单例模式获取消息管理器 MessageManager manager = MessageManager.getInstance(); |
| Code |
// 通过建造者模式构建消息 Message message = new MessageBuilder() .setContent("Hello") .append(" ") .append("World") .build(); // 通过命令模式执行输出 Command printCommand = new PrintCommand(factory.createPrinter(strategy), message); printCommand.execute(); |
如果把上面的代码转换为流程图,我们将看到图1-3所示的场景——软件工程中的“鲁布·戈德堡机械”。

图1-3 软件工程中的“鲁布·戈德堡机械”
当手段变成了目的,简单的功能就被淹没在抽象的海洋中。这种过度设计的讽刺例子,后来被称为“模式病”(Patternitis)。
GoF的贡献依然是不朽的。他们不仅给了我们23个设计模式,更重要的是,他们建立了一种思考软件的方法论。
■ 问题导向:每个设计模式都从一个反复出现的问题开始。
■ 上下文敏感:设计模式的适用性取决于具体场景。
■ 权衡思维:每个解决方案都有利有弊。
■ 演化视角:设计应该为变化留出空间。
这种方法论影响了整整一代开发者。即使在今天,当我们谈论微服务、函数式编程、响应式架构时,我们依然在使用GoF建立的词汇体系,依然在他们开创的思维框架中工作。
GoF是软件工程的启蒙者。他们让我们相信,混沌可以被组织,经验可以被传承,软件可以被“设计”而不仅仅是“编写”。这种对秩序的承诺,对理性的信仰,定义了一个时代。
然而,GoF的世界是一个“上帝视角”的静态世界。在这个世界里,类与类之间的关系是编译器确定的,行为是代码严格规定的。开发者相当于“独裁者”,他在代码运行之前就已经规划好所有的路径。
当理性被推到极致,当秩序成为教条,变革就会迫在眉睫。新的声音开始出现:“敏捷重于计划”“简单重于复杂”“演化重于设计”……
尤其是,这种“确定性的舒适区”恰恰成为AI时代的“枷锁”。当我们试图构建Agent时,我们需要的不是一个只会机械执行命令的“单例”,而是一个能够根据环境自主判断、动态决策的“伙伴”。GoF建立了秩序,但有的时候,过度的秩序就是牢笼。
软件世界正在酝酿下一个范式转换。
如果说GoF在荒原上竖起了第一块路标,那么在随后的10年里,工程师们则在这片土地上建起了宏伟的罗马城。
1995年至2005年,是设计模式发展的黄金时代。
面向对象编程不再是实验室里的新奇玩具,而是成为工业标准。Java在1995年横空出世,带着“一次编写,处处运行”的承诺;C++于1998年完成标准化,成为系统级编程的王者;C#在2000年诞生,集百家之长于一身。
整个软件世界都在说着“对象”的语言,而J2EE(Java 2 Platform,Enterprise Edition)的出现,更是将这种秩序推向了顶峰。J2EE不只是一套框架,更是一套严苛的工业标准。J2EE以严苛且统一的规范定义企业级软件开发:例如EJB(Enterprise JavaBeans),它不仅是代码,更像是严谨的标准化组件规范——开发者即便只为实现一个简单的业务功能,也必须编写Home接口、Remote接口和Bean实现类。这种设计虽然烦琐,但它将“设计模式”固化为“规范”——分布式对象、事务管理、安全控制,一切都被封装在标准的容器(Container)中。
对当时的大型企业来说,这正是他们梦寐以求的图景:软件开发终于不再是“艺术家”的个人表演,而是转变为可复制、可管理、可替换的工业级流程。
大学开始系统地教授设计模式——设计模式成为计算机科学课程的必修内容。招聘广告里满是对“精通J2EE架构”和“熟悉GoF设计模式”的要求。架构师们像中世纪的石匠大师,手握J2EE这一重型工具,用设计模式的积木搭建着日益宏伟却也愈发沉重的软件“大教堂”。
随着设计模式带来的思维革新,4次深刻的架构革命渐次展开。
MVC(Model-View-Controller,模型—视图—控制器)虽未列入GoF的23个设计模式,却是设计模式思想最早且最成功的应用之一。下面就通过一个简单的用户资料更新场景,直观理解模型、视图与控制器三者是如何协同工作的。
| Code |
// Model —— 数据与业务逻辑 class UserModel { private String name; private int age; public void updateProfile(String name, int age) { this.name = name; |
| Code |
this.age = age; notifyObservers(); // 观察者模式的应用 } } // View —— 展示层 class UserView { public void render(UserModel model) { System.out.println("Name: " + model.getName()); System.out.println("Age: " + model.getAge()); } } // Controller —— 控制层 class UserController { private UserModel model; private UserView view; public void handleUserInput(String name, int age) { model.updateProfile(name, age); view.render(model); } } |
MVC的成功在于它第一次让开发者意识到:复杂的应用可以“分层”设计。数据是数据,展示是展示,控制是控制。这种分离不仅让代码更清晰,更重要的是让不同的人可以并行工作——设计师专注于View,业务专家专注于Model,架构师协调Controller(见图1-4)。

图1-4 MVC:复杂的应用可以分层设计
DAO(Data Access Object,数据访问对象)模式进一步推进了这种抽象。它创造了一个“防腐层”,划清了界限:界限之上(业务层)只谈对象(User、Order);界限之下(持久层)才处理SQL、连接池。
| Code |
// 完全隔离数据访问逻辑 interface UserDAO { User findById(int id); void save(User user); void delete(User user); } // 可以随意切换具体实现 class MySQLUserDAO implements UserDAO { ... } class MongoUserDAO implements UserDAO { ... } class RedisUserDAO implements UserDAO { ... } |
DAO模式推进了“关注点分离”的发展。上述代码的精髓并不在于支持数据库切换,而在于使业务逻辑更加纯粹。DAO Interface就像一位外交官,伫立在“纯洁的Java对象王国”与“复杂的数据库泥潭”之间。
■ 对外(业务层),它使用优雅的面向对象语言(如save(User))。
■ 对内(数据库),它精通各种“方言”(如SQL、BSON、Key-Value)。
通过这种抽象,软件的核心逻辑不再受制于存储技术的演进。正因如此,尽管数据库技术持续迭代,企业的核心业务代码仍能保持长达十年的稳定性。这正是抽象的力量——以“不变”的接口,应对“万变”的实现。
Spring框架在2003年的出现,标志着依赖注入(Dependency Injection)从理论走向实践。
传统的编程方式是“我需要什么,我就创建什么”,请看下面的示例。
| Code |
class OrderService { private DatabaseConnection db = new MySQLConnection(); // 硬编码依赖 private EmailService email = new SMTPEmailService(); // 硬编码依赖 public void processOrder(Order order) { db.save(order); email.send(order.getCustomerEmail(), "Order confirmed"); } } |
在上述代码中,new关键字是耦合度最强的黏合剂。当你写下new MySQLConnection()时,实际上是在宣告:“我的这个OrderService类,生生世世都绑定在MySQL上了”。如果明天想换成Oracle,或者想在本地使用内存数据库进行测试,唯一的办法是修改源代码、重新编译。这就像把汽车引擎直接焊死在底盘上——如果想换个引擎,就得把整辆车熔了重造。
而IoC(Inversion of Control,控制反转)容器说:“告诉我,你需要什么,我来为你准备。”请看下面的示例。
| Code |
class OrderService { @Autowired private DatabaseConnection db; // 注入的依赖 @Autowired private EmailService email; // 注入的依赖 public void processOrder(Order order) { db.save(order); email.send(order.getCustomerEmail(), "Order confirmed"); } } |
这个变化看似微小,实则极具革命性。它让组件真正独立,让测试成为可能,让系统的组装变得像搭乐高积木一样灵活。
这很像好莱坞原则:“别给我们打电话,我们会打给你。”(Don’t call us, we’ll call you.)
■ 以前(主动权在对象):对象自己去寻找、创建自己需要的资源(“我要一个数据库,我去new一个”)。
■ 现在(主动权在容器):对象只需要在自己的额头上贴一张条子(@Autowired),写上“我需要一个数据库”。容器在启动时,会像慈母一样,扫描所有对象的额头,把做好的DatabaseConnection塞到它们的怀里。
控制权(Control)从对象反转(Invert)到容器中,如图1-5所示。
这种“被动”带来了巨大的好处,尤其是在测试层面。在没有IoC时,要测试OrderService的逻辑而不真正发送一封电子邮件几乎不可能,因为SMTPEmailService是在代码中“硬编码”的。
而在IoC的世界里,测试变得轻而易举,见如下示例。
| Code |
// 测试时,注入一个假的EmailService OrderService service = new OrderService(); service.setEmailService(new MockEmailService()); // 注入假的对象 service.processOrder(order); // 验证:并没有真的发送电子邮件,只是记录了调用行为 |
这让软件开发从“手工艺雕刻”变成了“工业化组装”。我们不再是在写“死代码”,而是在编排(Wiring)组件。只要接口对得上,任何组件都可以替换。
这种“组件可插拔”的思想,为后来微服务的兴起奠定了基础,也为今天Agent系统中“工具的动态挂载”埋下了伏笔——毕竟,Agent选择策略/工具的过程,本质上就是一种动态的依赖注入。


图1-5 控制权从对象反转到容器中
策略模式的广泛应用,让“算法”成为可替换的“零件”。如何理解这句话?先来看一段通过if-else语句实现的传统代码。
| Code |
// 传统方式:丑陋的if-else语句 class PriceCalculator { public double calculate(Customer customer, double amount) { if (customer.getType() == CustomerType.REGULAR) { return amount * 0.95; } else if (customer.getType() == CustomerType.VIP) { return amount * 0.80; } else if (customer.getType() == CustomerType.SUPER_VIP) { |
| Code |
return amount * 0.70; } return amount; } } |
在if-else语句的世界里,程序的行为在编译的那一刻就被锁死了。但在策略模式的世界里,对象的行为可以在运行时发生改变。
| Code |
// 策略模式:优雅的多态 interface PricingStrategy { double calculate(double amount); } class RegularPricing implements PricingStrategy { public double calculate(double amount) { return amount * 0.95; } } class VIPPricing implements PricingStrategy { public double calculate(double amount) { return amount * 0.80; } } // 行为完全可配置 class PriceCalculator { private PricingStrategy strategy; //请注意setStrategy这个方法 public void setStrategy(PricingStrategy strategy) { this.strategy = strategy; } public double calculate(double amount) { return strategy.calculate(amount); } } |
上述代码的改变看似微小,实则触及了软件设计的核心原则——开闭原则(Open-Closed Principle,OCP)。
■ 在传统方式下,PriceCalculator是一座封闭的堡垒。每当我们想要增加一种新的会员类型(如“黑金会员”)时,就必须像拆弹专家一样,小心翼翼地拆开这个类,修改核心的if-else语句逻辑。每一次修改,都是一次引入bug的冒险。
■ 在策略模式下,PriceCalculator变成一个开放的插槽(Context)。它不用知道具体的打折规则,而只需要知道“我需要一个打折策略”。想要增加新规则时,只需要编写一个新的类实现PricingStrategy即可。原有的代码则一行都不用修改。
这种设计将“变化的”(具体的打折算法)与“不变的”(计算价格的流程)彻底剥离了(见图1-6)。

图1-6 策略模式下对象的能力配置方式十分灵活
想象一个游戏角色。
■ 当他拿起“弓箭”时,他的攻击策略变成远程攻击(RangedAttack)。
■ 当他拿起“大剑”时,他的攻击策略瞬间切换为近战攻击(MeleeAttack)。
对象不再是僵化的,它拥有了根据环境改变行为的能力。这其实就是Agent概念的雏形。
■ GoF时代:开发者手动调用setStrategy来切换算法。
■ Agent时代:Agent观察环境,通过推理,自行决定选择哪一种策略/工具来解决问题。
UML(Unified Modeling Language,统一建模语言)作为软件设计师的“世界语”,提供了一系列标准化的类图、序列图、状态图……软件设计第一次可以在不编写一行代码的情况下被完整表达(见图1-7)。

图1-7 UML成为软件设计师的标准化语言
Rational Rose、Together、StarUML等工具让“画图编程”成为可能。架构师们在白板上画出系统的骨架,然后工具自动生成代码框架。这是软件工程最接近建筑工程的时刻——先设计,后建造。
在设计模式的黄金时代,软件设计中的工匠精神体现得淋漓尽致。
■ 马丁·福勒(Martin Fowler)的《重构:改善既有代码的设计》教会我们如何打磨代码。
■ 罗伯特·马丁(Robert Martin)的《敏捷软件开发:原则、模式与实践》阐述了SOLID原则的精妙之处。
■ 埃里克·埃文斯(Eric Evans)的《领域驱动设计:软件核心复杂性应对之道》则将设计模式的思想引入业务建模领域。
……
开发者们仿佛中世纪工匠行会的成员,掌握着属于自己的“秘传知识”。
■ 单一职责原则(Single Responsibility Principle,SRP):一个类仅承担一项职责。
■ 开闭原则:对扩展开放,对修改关闭。
■ 里氏替换原则(Liskov Substitution Principle,LSP):子类必须能够替代其父类,而不影响程序的正确性。
■ 接口隔离原则(Interface Segregation Principle,ISP):不强迫客户端依赖其用不到的接口。
■ 依赖倒置原则(Dependency Inversion Principle,DIP):应依赖抽象,而非具体实现。
代码如同建筑,而设计模式则是蓝图。每一行代码都经过深思熟虑,每一个类的存在都有其道理。代码评审犹如艺术批评,重构恰似对雕塑的精细打磨。
然而,当手段被神圣化时,目标往往容易被忽略。这种对代码“纯洁性”的极致追求虽然带来了秩序和美感,但也无形中筑起了一道高墙——它将软件工程推向了一个象牙塔般的高度,使得开发者们沉醉于打造完美的水晶球,逐渐忽视应对现实世界复杂性的灵活性。
正当设计模式与J2EE工业标准将软件工程推向巅峰时,行业内却开始弥漫一种隐秘的疲惫感。GoF在《设计模式:可复用面向对象软件的基础》中描绘了一个理性的“静态世界”:系统边界清晰,需求变更可预测,运行环境稳定不变。在这个框架下,我们追求极致的解耦和完美的抽象层次。然而,这种对秩序和规范的狂热追求逐渐演变成一种“重型工程美学”。
为了严格遵循所谓的“最佳实践”,即便是最简单的“Hello World”程序也可能需要配置多达5个XML文件;而为了达到“解耦”的目标,一个基本的业务操作可能不得不穿越7层架构。架构师们沉浸于绘制出完美的UML图,但往往忽视了代码库因此变得异常臃肿的问题。
此时的软件工程仿佛一座宏伟的巴洛克建筑:精美、复杂,但也极其繁重。开发者们自认为建造的是永恒的金字塔,实际上不过是陷入了过度设计的陷阱。这种“内部僵化”让许多开发者感到窒息,《敏捷宣言》(Agile Manifesto)的出现正是对这种束缚的一种反抗。
但是,真正的危机并非来自既有的系统内部,而是来自外界的变化。当开发者们还在为如何实现一个“单例模式”进行激烈讨论时,外面的世界已经发生了翻天覆地的变化。
诸神并非死于战争,而是死于环境的变迁。
在很长一段时间里,我们以为软件工程的终极目标是打造一座永恒的精密钟表:齿轮咬合,分秒不差,一切尽在掌握之中。GoF的设计模式以及此后逐步构建起的软件工程体系,正是这座钟表的蓝图。我们相信,只要设计足够严谨,就没有无法处理的逻辑,也没有不可预测的未来。
可惜,互联网的洪流早已悄然改道。新世界不再是静态的钟表,而是咆哮的海洋。在这里,旧时代的秩序法则不仅失效,甚至成为毁灭的根源。
2008年10月,Twitter(现X)宕机了。
不是普通的宕机——服务器仍在运行,数据仍在响应,每个组件都看似正常工作,但整个系统如陷泥潭,响应时间从毫秒级退化至分钟级。开发者们反复检查代码,发现每个设计模式都精准地待在它该在的位置:观察者模式用于事件通知,工厂方法模式负责创建对象,单例模式管理连接池……
代码是完美的,架构是优雅的,但服务器是死寂的。问题出在哪里?
问题在于,当上百万用户同时涌入时,精心设计的观察者模式变成了雪崩的导火索。一条推文触发一连串通知,每个通知又触发更多通知,原本精巧的设计模式演变成了一场完美的风暴。
这是第一声警报:确定性设计遇到了充满不确定性的场景。
GoF的设计模式诞生于一个“静态世界”。
■ 系统边界明确:企业内部系统,用户可知,功能可控。
■ 行为路径可预测:用户点击按钮A,系统执行流程B。
■ 需求变更可预测:一年发布若干版本,变更经过严格评审。
■ 运行环境稳定:单机或小规模集群,故障模式简单。
然而,21世纪的第一个十年,一切都变了。
■ Web 2.0带来了用户爆炸:用户数从千人级跃升至亿人级,范围从固定用户扩展到全球用户,服务时间从朝九晚五变为7×24小时不间断。
■ 分布式成为常态:系统架构从单机演进到数千台服务器,通信方式从同步调用转向异步消息,事务模型从ACID[2]转向BASE[3],一致性策略从强一致性转为最终一致性。
[2] ACID是Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)的首字母缩写,主要用于传统的数据库事务处理,强调数据的强一致性和事务的完整性。
[3] BASE是Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)的缩写,主要用于分布式系统和高并发场景,强调系统的可用性和伸缩性,允许数据在一定时间内出现短暂的不一致性。
■ 敏捷思想改变了开发节奏:发布频率从年度变为每日部署,开发模式从瀑布式转向持续迭代,设计原则从“详尽完备”变为“够用就好”,架构方式从预先设计转向演进式设计。
■ 移动互联网改变了交互模式:输入方式从键盘、鼠标变为触摸、手势,显示设备从大屏幕转向小屏幕,网络连接从有线变为无线,使用场景从固定位置变为随时随地。
不确定性不是突然降临的,它像满月之日的潮水,从3个方向同时涌来。
传统设计假设用户数是可控的,负载是可预测的。例如,银行系统知道银行有多少客户,企业软件知道企业有多少员工。设计模式在这种“有界”的世界里游刃有余。
但互联网打破了边界,请看下面的示例。
| Code |
// 传统设计:假设用户数量可控 class NotificationService { private static final int MAX_OBSERVERS = 10000; private Observer[] observers = new Observer[MAX_OBSERVERS]; public void notifyAll(Event event) { for (Observer observer : observers) { observer.update(event); // 同步通知,假设用户数有限 } } } |
而现实是,用户数可能是10,也可能是1000万。互联网时代的一条推文可能需要通知上千万粉丝,在这种情况下,传统的观察者模式瞬间崩溃。
传统设计假设用户行为是可预测的。点击按钮A,执行流程B。用户像机器,系统像齿轮,一切井然有序。
但社交网络改变了一切,请看下面的示例。
| Code |
// 传统设计:线性的用户路径 class ShoppingCart { public void processOrder() { // 明确的流程:浏览→添加→结账→支付 validateItems(); calculatePrice(); processPayment(); sendConfirmation(); } } |
而现实是,用户行为是非线性的、突发的、群体性的。一位明星的推荐可能让数百万用户同时涌向同一商品;Pinterest的瀑布流让用户行为从“搜索”变成“漫游”。设计软件时所假设的“理性用户”已不复存在。
传统设计假设需求是可定义的。花几个月进行分析,画出完整的UML图,然后按图施工。
但移动互联网摧毁了这个假设。请看下面的示例。
| Code |
// iPhone发布前的设计 interface MobileApp { void displayOnScreen(int width, int height); // 假设:屏幕尺寸固定 void handleKeyPress(int keyCode); // 假设:键盘输入 void saveToStorage(File file); // 假设:文件系统 } // iPhone发布后的现实 interface MobileApp { void handleTouch(MultiTouchEvent event); // 多点触控 void handleRotation(OrientationEvent event); // 屏幕旋转 void handleGesture(GestureEvent event); // 手势识别 void handleSensor(SensorEvent event); // 各种传感器 void handleNotification(PushEvent event); // 推送通知 // 需求在爆炸式增长 } |
需求的不确定性,比前两个不确定性更致命。规模的不确定性会压垮架构,行为的不确定性会扰乱流程,但需求的不确定性会让系统“失去方向感”。在移动互联网时代,需求已经不是“定义出来的”,而是“生长出来的”——今天用户要直播,明天要短视频,后天要AI滤镜,大后天要虚拟形象。没有人能提前画好完整的UML图,因为不知道产品会长成什么样。
传统设计的假设是线性的:需求先确定→架构再设计→系统再扩展。互联网时代的现实却是反向的:需求永远在变→架构必须适应变化→才能继续存活。这意味着,软件开发不再是“盖房子”,而更像是“驯服一头不断进化的生物体”。
■ 功能会爆炸式增长。
■ 需求会不断自我否定。
■ 未来形态完全不可预测。
■ 任何一次技术大潮都可能重塑整个交互方式(如触控、手势、传感器、AI)。
因此,需求的不确定性迫使设计模式从“可预见的结构”变成“可演化的结构”。开发者们不能再追求完备,而要追求适应性;不能再试图预测未来,而要让系统有能力在未来重新组合。
在不确定性面前,经典设计模式开始一个接一个地失效。
首先是单例模式的悖论。
| Code |
// 传统单例模式:在单机时代完美工作 class DatabaseConnection { private static DatabaseConnection instance; public static synchronized DatabaseConnection getInstance() { if (instance == null) { instance = new DatabaseConnection(); } return instance; } } |
在分布式时代,单例变成了瓶颈。100台服务器,不可能每台都有自己的“单例”;全局锁导致性能瓶颈;同时,单例模式也会增加单点故障风险。对此,Netflix的解决方案:放弃单例模式,拥抱冗余。
其次是工厂方法模式的僵化。
| Code |
// 传统工厂方法模式:产品类型在编译时确定 interface CarFactory { Car createSedan(); Car createSUV(); Car createTruck(); } |
在AI时代,产品类型在运行时学习。Tesla的自动驾驶系统每天都在学习新的“驾驶模式”,传统工厂方法模式无法处理“运行时演化”的产品。
最后是观察者模式的雪崩。
| Code |
// 传统观察者模式:假设通知是即时且可靠的 class EventPublisher { private List<Observer> observers = new ArrayList<>(); public void publish(Event event) { for (Observer obs : observers) { obs.handleEvent(event); // 如果一个观察者阻塞,则全部观察者阻塞 } } } |
现实情况是,Facebook(现属Meta)的News Feed中单个用户的动态可能需要推送给数百万其他用户,同步观察者模式会导致系统雪崩。Facebook的解决方案:消息队列、最终一致性和流处理。
上述例子反映出来的,不仅仅是技术问题,更是哲学问题。
GoF的设计模式诞生于机械论世界观:系统是零件的组合,整体等于部分之和,因果关系是线性的,未来是可预测的。
但真实世界更像有机体:系统是关系的网络,整体大于部分之和,因果关系是循环的,未来则是涌现的(见图1-8)。

图1-8 软件设计思维方式的哲学变迁
传统软件工程相信“设计先于实现”:需求分析→系统设计→详细设计→编码实现→测试部署。
但互联网产品遵循“演化胜于设计”:最小可行产品(Minimum Viable Product,MVP)→用户反馈→快速迭代→数据驱动→持续演化。Facebook的座右铭“Move Fast and Break Things”(快速行动,打破常规)是对传统设计哲学的公开挑战。
设计模式试图“控制”复杂性:通过接口控制依赖,通过封装控制状态,通过继承控制变化。
但复杂系统需要“适应”:通过反馈适应环境,通过学习适应变化,通过演化适应未知。
软件世界正在从“静态设计”走向“动态演化”,从“确定性”走向“适应性”。而在这个新世界里,我们需要的不仅是设计模式,更是一种新的思维方式——拥抱变化、适应不确定性。
如果说2008年的Twitter宕机是软件工程“旧时代”崩溃的信号,那么2012年至2014年则是新秩序在“废墟”中建立的3年,这一时期发生的一系列事件彻底重构了软件工程的底层逻辑。
2012年,Google发表论文“The Datacenter as a Computer”。
此前,开发者的视角聚焦在“单个节点”上。开发者像钟表匠一样,试图让每一台服务器、每一个进程都完美运行。如果一台服务器坏了,那将被视为严重的事故。
Google告诉世界:并不是服务器在运行软件,数据中心本身才是那台“计算机”。
在这个新视角下,硬件故障不再是意外,而是统计学上的必然。硬盘一定会坏,网络一定会抖动。软件设计不再是为了“防止故障”,而是为了在“故障常态化”的基础上依然提供可靠服务。这是从确定性逻辑向概率性逻辑迈出的第一步。
2012年,Netflix开源Chaos Monkey。
这可能是软件史上最疯狂的决定之一:一家公司竟然编写了一个程序——专门在生产环境中随机杀死自己的服务器!
在GoF的时代,这是不可想象的亵渎。但Netflix的逻辑是:既然故障不可避免,那就让故障来得更频繁些吧。
这标志着“反脆弱”(Antifragility)思想正式进入软件工程领域。系统不再追求像钻石一样坚硬(一旦破碎就无法修复),而是追求像免疫系统一样,通过持续不断的微小创伤来进化出更强的生存能力。确定性设计试图封锁混乱,而反脆弱设计试图利用混乱。
2013年,Docker发布。
在Docker之前,服务器是需要“运维”的。开发者登录上去,安装依赖,修改配置,修复补丁。随着时间的推移,服务器变成了充满“独家记忆”的雪花(Snowflake),没人敢重启它,因为没人知道它到底变成了什么样。
Docker带来的“集装箱革命”,让基础设施变得不可变。如果一个容器坏了,我们不是修理它,而是直接销毁,然后启动一个新的。软件组件从此变成了无状态的“耗材”,而不是需要维护的资产。这为后来Agent能够动态地调用、销毁工具环境提供了物理基础。
2014年,Kubernetes(简称K8s)诞生。
Kubernetes弃用了传统的命令式编程——“先启动A,再启动B,如果失败则重试C”,转而采用声明式API——“我需要3个副本,你看着办”。
这引入了极其重要的“调解循环”(Reconciliation Loop)概念。
■ 期望状态(Desired State):用户定义的意图。
■ 当前状态(Current State):系统的实际情况。
■ 调解者(Controller):不断感知差距,并采取行动弥合差距。
请注意,这个“感知-差异-行动”的循环,其实和Agent心智模型的雏形有些相似!Kubernetes的调解者就是一个个原始的、针对特定任务的Agent。
这些变革汇聚成同一个声音:确定性时代结束了。软件不再是精心设计的精密瑞士手表,而更像是自组织的生物群落。
■ 稳定性不再来自单一组件的完美,而来自群体的冗余与协作。
■ 控制权不再来自严格的指令,而来自对“意图”的声明与动态调节。
我们终于学会了不再试图扮演“上帝”去控制每一个电子的跳动,而是学会了如何引导一个充满不确定性的复杂系统:复杂系统的稳定性不是来自完美的设计,而是来自持续的适应。
在不确定性的土壤里,一种适应新环境的“物种大爆发”开始了。
如果说GoF的设计模式是为了在单机时代“对抗”变化,那么新一代的设计模式则是为了在分布式时代“拥抱”变化。那些曾经被奉为圭臬的经典设计模式,在云原生的浪潮下显得左支右绌:单例模式在分布式集群中失去了唯一性,观察者模式在异步网络中丢失了实时性,严格的抽象工厂模式在微服务的碎片化调用前显得笨重不堪。
于是,新的生存法则催生了新的设计模式。
■ 从“控制流程”到“响应变化”:我们不再试图编写严格的指令序列(先做A,再做B),而是建立一套数据管道系统。数据像水流一样流过,系统只负责响应数据的到来。无论是RxJava还是现代流式计算框架,核心都在于处理异步与背压,而非控制步骤。
■ 从“共享状态”到“消息传递”:既然共享内存锁不住,那就不要共享。Actor模型和微服务架构回归了对象最原始的定义——通过消息进行通信。组件之间可谓“老死不相往来”,仅通过异步信箱交换信息,从而实现了真正的隔离与解耦。
■ 从“修改变量”到“不可变数据”:为了应对并发带来的混乱,我们开始学习函数式编程的智慧。不再试图修改状态,而是每次产生新的状态。就像区块链或Git的提交记录方式,历史不可篡改,因此也就消灭了“竞争条件”产生的bug。
■ 从“预防故障”到“与之共舞”:断路器(Circuit Breaker)、舱壁隔离(Bulkhead)、重试(Retry)……这些设计模式不再假设系统是完美的,而是假设故障随时会发生。它们是软件系统的“安全气囊”,承认脆弱性,才能获得反脆弱性。
这些新的设计模式都有一个共同的哲学内核:它们放弃了“上帝视角”的全局控制,转而追求局部的自治与适应。它们不试图消除不确定性,而是拥抱它。
设计模式没有死去,它只是在进化:从GoF的23张“建筑图纸”,演变成云原生时代的“生存指南”。
2016年,AlphaGo战胜李世石。
这不仅是AI的胜利,更是一种新的问题解决设计模式的胜利。AlphaGo没有依靠预设的“定式”(模式),而是通过数百万局的自我对弈,涌现出了超越人类理解的策略。
这给软件工程带来了巨大的冲击:如果最复杂的问题(围棋)都不能靠“设计”来解决,而是靠“学习”和“演化”,那么我们构建软件的方式是否也到了该彻底重构的时候?
软件工程站在十字路口。
■ 继续沿着GoF的道路,追求更精致的静态设计?
■ 拥抱不确定性,探索动态演化的新模式?
旧的范式正在黄昏中隐退,新的力量正从代码的缝隙间悄然生长——那是函数式的纯粹、响应式的流动,以及分布式的共识。
从对象(object)到智能体(Agent),从设计(design)到演化(evolution),从确定(certainty)到适应(adaptation)——软件工程的新纪元即将开启。
而设计模式,这个曾经统治了软件世界20年的概念,将以什么样的形式在新世界中重生?
当我们不再需要编写代码,而是需要“指导”一个拥有自主意图的Agent时,我们还需要设计模式吗?
答案是肯定的。但我们需要的,不再是代码的模具,而是思维的罗盘。
在迎接真正的Agent降临之前,我们必须先完成一场彻底的思维武装。
在第2章中,让我们开始这场范式的迁移。