Software Architecture / AI Coding / Design Patterns

AI Coding 时代, 架构能力才是程序员的核心竞争力

如果说过去的软件开发难点在“把功能写出来”,那么今天更难的部分已经变成“让快速生成的大量代码仍然可演化、可协作、可验证”。 设计模式的价值,不再只是面试题或教科书知识,而是帮助人类在 AI 时代重新掌控结构、边界和变化。

这篇文章讲什么 从 GoF 原书的 23 种模式出发,系统解释它们在 AI Coding 时代各自隔离了什么变化。
怎么讲 每个模式都从第一性问题、结构意图、适用边界、误用代价和现代框架映射来展开。
为什么要这样讲 因为今天最需要的不是会背定义,而是能在 AI 生成代码时做对结构判断。

一、为什么 AI Coding 时代反而更需要设计模式

很多人会误以为,既然 AI 已经可以大段生成代码,设计模式的重要性应该下降。现实恰好相反。AI 让“实现能力”变得廉价,结构失误却因此被成倍放大。 过去一个开发者亲手写几十个分支时,至少会在编码过程中逐渐感受到系统正在失控;今天模型可以在几十秒里生成上百行看似合理的代码,如果没有模式思维约束,系统会更快进入不可维护状态。

设计模式本质上不是“优雅类图”,而是工程世界对重复变化结构的压缩表达。它们关心的不是语言技巧,而是下面这些更根本的问题:哪里在变,谁该知道变化,变化该如何被隔离,系统该如何测试,以及多人协作时影响面如何被控制。

AI Coding 中常见问题 典型表现 背后缺失的结构判断 模式能解决什么
功能越加越乱 每来一个需求就多一个分支 没有识别出算法或流程中的变化点 StrategyTemplate Method
依赖横向扩散 到处直接创建具体对象 创建与使用没有分离 Factory MethodAbstract Factory
横切逻辑失控 日志、缓存、鉴权散在各处 没有把增强或控制逻辑收束到边界层 DecoratorProxyFacade
一个更尖锐的说法: 在 AI Coding 时代,设计模式的重要性不是体现在“让代码更漂亮”,而是体现在“防止模型把错误结构快速复制几十遍”。

二、理解设计模式,首先要理解“变化点”

大多数教材会从定义开始讲模式,但工程上更有效的顺序是反过来:先看变化点,再看结构。你真的理解一个设计模式,不是因为你记住了它的标准定义,而是因为你能回答五个问题。

  1. 这个模式到底在隔离哪一种变化?是算法、创建过程、通知链路,还是访问控制?
  2. 它额外引入了什么结构成本?接口、对象族、代理层还是继承层级?
  3. 它和相邻模式的边界在哪里?例如 Decorator 与 Proxy,Factory Method 与 Abstract Factory。
  4. 它在现代框架里通常如何出现?是显式类层级,还是配置、容器、回调、装饰器语法糖。
  5. 如果不用它,系统通常会退化成什么坏味道?条件泥潭、重复编排、全局状态污染,还是耦合扩散。
本文的核心原则: 设计模式不是先验宗教,而是变化管理工具。真正的高水平不是“到处套模式”,而是“看见变化时知道该不该上模式,以及该上哪一种模式”。

三、GoF 23 种设计模式全景图:先看变化,再看结构

GoF 原书把经典设计模式分成创建型 5 个、结构型 7 个、行为型 11 个。[1] 真正值得记住的不是“23 这个数字”,而是每个模式分别把哪一种变化从主流程里拿走:是创建决策、接口不兼容、访问控制、状态切换,还是事件扩散。 如果只背定义,很快会忘;如果抓住“模式隔离的变化点”,大多数模式都能在脑子里自动归位。

分类 模式 第一性问题 核心结构动作 不用时常见退化
创建型Abstract Factory如何切换一整套兼容对象族把对象族创建集中到统一工厂接口风格错配、供应商混搭、装配分散
创建型Builder复杂对象如何分步骤构建把构建过程与最终表示解耦长参数列表、构造顺序混乱
创建型Factory Method由谁决定具体创建哪一类产品把实例化决策延迟到子类或工厂实现调用方直接依赖具体类
创建型Prototype复制是否比重新构造更合适用原型对象派生新实例初始化昂贵、模板对象重复拼装
创建型Singleton哪些资源语义上必须全局唯一控制实例数与访问入口全局状态污染、测试相互干扰
结构型Adapter旧接口与新接口如何接上做接口翻译,不动既有实现大面积改旧系统、边界层破碎
结构型Bridge两个维度同时变化如何避免类爆炸把抽象维与实现维拆开后再组合继承层级按笛卡尔积膨胀
结构型Composite部分与整体如何统一对待为叶子与容器定义共同接口遍历和聚合逻辑充满类型分支
结构型Decorator如何按需叠加职责用包装对象逐层增强行为组合子类泛滥、横切逻辑扩散
结构型Facade复杂子系统如何只暴露一个清晰入口对外收敛为高层接口调用方重复编排底层协作流程
结构型Flyweight海量细粒度对象如何共享状态拆分内部共享状态与外部变化状态内存占用随对象数线性暴涨
结构型Proxy接口不变时如何改变访问语义在真实对象前加一层控制代理权限、远程性、懒加载散落各处
行为型Chain of Responsibility由谁处理请求应否写死把请求沿处理链逐步传递大量 if-else 或巨型分发器
行为型Command请求本身是否需要被记录、排队、撤销把动作对象化调用难审计、难重放、难撤销
行为型Interpreter业务规则能否抽成一门小语言把语法规则和求值过程对象化规则分支散在代码里无法组合
行为型Iterator如何遍历集合而不暴露内部表示为遍历定义统一协议每种容器一套遍历 API
行为型Mediator对象之间的网状耦合如何收束把协作规则集中到中介者对象互相认识、关系图失控
行为型Memento如何在不破坏封装下保存与恢复状态把快照封装成外部可持有对象撤销点粗糙、状态恢复不完整
行为型Observer一个事实发生后如何通知多方响应发布与订阅解耦主流程不断追加“顺手做”的动作
行为型State状态变化时行为为何总在分支里爆炸把状态相关行为移到状态对象状态机写成巨型条件语句
行为型Strategy同一目标下多算法如何替换把算法族抽到统一接口后注入条件泥潭、算法散落、难扩展
行为型Template Method稳定流程骨架与可变步骤如何共存父类固定顺序、子类实现局部流程复制、步骤顺序失控
行为型Visitor结构稳定时如何持续增加新操作把操作从对象结构中抽离每加一个操作都要改一遍节点类

创建型模式

你真正要问的不是“怎么 new”,而是“由谁决定创建什么、创建过程是否稳定、创建结果是否需要成套一致”。

Abstract Factory / Builder / Factory Method / Prototype / Singleton

结构型模式

核心不是类图好不好看,而是对象彼此如何连接,才能在接口兼容、访问控制、层次组织和横切增强上不失控。

Adapter / Bridge / Composite / Decorator / Facade / Flyweight / Proxy

行为型模式

关注的是职责如何流动、算法如何替换、状态如何切换、消息如何传播,以及一段行为是否应该被对象化。

Chain of Responsibility / Command / Interpreter / Iterator / Mediator / Memento / Observer / State / Strategy / Template Method / Visitor

第一性原则

模式不是为了减少代码行数,而是为了把变化点封装进最小认知边界,让第五次迭代也不至于拖垮系统。

AI Coding 时代更要反着学:先识别变化和退化路径,再决定模式,而不是先背术语再找题目去套。

一个总抓手: 判断一个模式是否值得引入,至少回答三件事:它隔离了什么变化,它把复杂度搬到了哪里,它是否真的让替换、测试和协作更容易。

Abstract Factory:当你要切换的不是一个对象,而是一整套彼此兼容的系统

关键词:对象族、一致性、供应商切换、成套装配。

Abstract Factory 的第一性问题是“一致性本身是不是需求”。如果调用方切换的不是单个按钮,而是按钮、对话框、输入框、主题色、图标规范这一整套配套对象,那么你要管理的就不是创建一个类,而是创建一整个兼容对象族。 这时最可怕的不是“多写几个构造函数”,而是局部看都对、整体风格却混乱。

它和 Factory Method 的边界非常清晰:Factory Method 解决“这一个对象到底创建谁”,Abstract Factory 解决“这一组对象必须同时切到哪一套家族”。前者偏单点延迟决策,后者偏整体一致性。前端主题系统、数据库方言层、多云厂商封装都是它的高频落点。

  • 变化点:产品家族整体切换,而不是单个产品替换。
  • 适合:主题系统、跨平台 UI、数据库方言、多供应商 SDK。
  • 误用风险:如果只有一个产品在变,用 Abstract Factory 只会平添抽象层。
  • 边界辨析:它解决的是“家族一致性”,不是“复杂构建步骤”。

Builder:当对象复杂到“怎么组装”本身已经成为问题

关键词:分步骤构造、构建顺序、最终表示、参数风暴治理。

Builder 的核心不是 fluent API 好不好看,而是“构建过程能不能独立管理”。如果一个对象需要分阶段校验、按顺序填充、允许可选步骤组合,或者同一组构建步骤能产出不同表示,说明复杂度已经从“对象是什么”转移到了“对象怎么被拼出来”。

这时,把一长串参数都塞进构造器只是把复杂度藏起来。Builder 则显式承认构建过程本身就是变化点。它非常适合请求对象、复杂查询、报表配置、工作流定义和测试夹具搭建。

  • 变化点:构建顺序、可选步骤、最终表示。
  • 适合:复杂配置对象、SQL/DSL 组装、报表定义、测试数据构造。
  • 误用风险:对象并不复杂时,Builder 会把简单构造人为拉长。
  • 边界辨析:它关注“步骤化构建”,不是“成套对象族切换”。

Factory Method:让调用方不必知道“具体类怎么被选出来”

关键词:延迟创建、具体类解耦、扩展入口收束。

Factory Method 的第一性问题是“创建决策应不应该落在使用方手里”。当调用方只需要一个导出器、解析器、客户端、存储后端时,它不该同时承担环境探测、配置读取、生命周期初始化和具体实现选择。

在框架里,这个模式几乎无处不在,因为框架天然需要把“创建时机”和“创建谁”留成扩展点。Flask 的 Application Factory、容器中的 bean 创建思想,都体现了把实例化决策从业务调用方移走的价值。[9][4]

  • 变化点:具体产品类型、装配时机、创建分支。
  • 适合:报表、解析器、存储客户端、消息发送器。
  • 误用风险:如果只有一个稳定实现,为工厂而工厂只会制造噪音。
  • 边界辨析:它解决“单对象创建决策”,不是“复杂对象拼装”。

Prototype:当复制一个成熟对象,比重新构造更便宜也更稳定

关键词:样板对象、复制派生、初始化成本、模板变体。

Prototype 不是“会拷贝对象”这么简单,它真正解决的是“重新构造的成本是不是过高”。如果某个对象初始化昂贵、默认组合很多、局部变体频繁,那么从一个已经完成大部分准备工作的原型派生,通常比每次从零 new 再挨个设置字段更可靠。

这个模式在图形编辑器、规则配置、游戏对象、复杂模板生成里尤其自然。它把“共同部分”沉到原型对象里,再把“差异部分”留给复制后的少量改动。现代语言中如果复制能力已经很好,Prototype 的存在感会减弱,但其设计意图仍然成立。

  • 变化点:初始化代价和模板变体,而不是产品接口。
  • 适合:图元复制、默认配置派生、规则模板、表单模版。
  • 误用风险:浅拷贝/深拷贝边界不清时,容易复制出共享状态 bug。
  • 边界辨析:它解决“从现成对象派生”,不是“分步骤创建”。

Singleton:只有在“语义上必须唯一”时才成立,而不是因为传依赖麻烦

关键词:全局唯一、生命周期控制、状态污染风险。

Singleton 最常被误学成“一个对象只能有一个实例”,但更准确的理解是“有极少数资源在语义上必须以全局唯一身份出现”。例如进程级配置源、注册表、底层资源门面。这是业务约束,不是偷懒理由。

今天更稳妥的工程做法通常是让容器管理 singleton scope,而不是每个类都手写单例。因为一旦实例里带可变状态,测试污染、并发时序、生命周期残留会迅速放大。GoF 作者后来也反复提醒过 Singleton 非常容易沦为坏味道。[2]

  • 变化点:实例数和生命周期,而不是业务算法。
  • 适合:配置源、注册表、不可重复初始化的底层门面。
  • 误用风险:把依赖注入问题伪装成“全局唯一”。
  • 边界辨析:业务对象默认不该因为方便访问就被做成单例。[4]

Adapter:新系统想要干净接口,但现实世界通常是一堆旧接口

关键词:接口翻译、边界兼容、遗留系统缓冲层。

Adapter 的第一性问题不是“代码风格统一”,而是“旧系统能不能不动”。真正的工程环境里,你经常面对遗留系统、第三方 SDK、老协议和新服务。它们不是坏,只是接口形状与你现在想要的抽象不一致。

Adapter 的价值就在于把“不一致”封在边界层,让系统内部继续沿着更干净的接口发展。Requests 的 Transport Adapter 和 Spring MVC 的 HandlerAdapter 都是工业化示例。[10][6]

  • 变化点:接口形状与参数语义,而不是业务职责。
  • 适合:遗留接入、第三方网关统一封装、协议迁移期兼容。
  • 误用风险:如果新旧接口差异其实是业务概念差异,单纯翻译会掩盖模型问题。
  • 边界辨析:Adapter 是翻译,Decorator 是增强,Proxy 是控权。

Bridge:当变化轴不止一条时,先拆轴,再组合

关键词:抽象维、实现维、正交变化、类爆炸治理。

Bridge 专门处理“两个维度都在变”的情况。比如消息类型会变,发送渠道也会变;图形形状会变,渲染后端也会变。如果直接用继承,把两个维度绑在一棵树上,类数量会按笛卡尔积膨胀。

Bridge 的核心动作是:把抽象维和实现维拆开,让它们分别演化,再通过组合连接。这样新增一个渠道不需要复制全部消息类型,新增一个抽象也不需要重新派生所有实现类。

  • 变化点:两个长期独立变化的维度。
  • 适合:多渠道通知、多平台渲染、多驱动设备控制。
  • 误用风险:如果只有一个维度在变,Bridge 会比普通组合更重。
  • 边界辨析:它和 Strategy 都用组合,但 Bridge 管“两个轴”,Strategy 管“一个算法位”。

Composite:当树形结构中的“单个”和“整体”都必须被统一处理

关键词:部分-整体、递归组合、统一接口、树结构。

Composite 的第一性问题是“调用方是否必须反复区分当前面对的是叶子还是容器”。如果一个系统天然是树形的,如文件与文件夹、图元与图层、组件与子组件,那么每次都在外面判断类型,说明结构抽象还不够稳定。

Composite 通过共同接口把“局部”和“整体”统一起来。你得到的真正好处,不是类图看起来工整,而是渲染、聚合、遍历、批处理都能用递归自然展开,认知成本显著下降。

  • 变化点:节点层次和递归聚合行为。
  • 适合:文件系统、UI 组件树、组织架构树、表达式树。
  • 误用风险:叶子与容器能力差异过大时,硬统一接口会制造空方法。
  • 边界辨析:Composite 统一结构,Visitor 统一的是“对这棵结构做什么”。

Decorator:当能力需要按需叠加,而不是预先长成一棵继承树

关键词:职责增强、横切逻辑、按需叠加、组合优先。

Decorator 真正解决的是“增强能力是否可以被拆成独立层并自由组合”。如果你需要给对象加日志、缓存、重试、限流、鉴权、压缩、埋点,继承会很快变成组合子类爆炸,而一层层包装则能把每种增强职责拆干净。

Python 里的装饰器语法就是这种思想大规模普及后的语法糖。重要的不是有没有 @decorator,而是增强逻辑是否能独立存在、是否能调整顺序、是否能在不改原对象的前提下撤掉。

  • 变化点:横切增强能力及其叠加顺序。
  • 适合:日志、缓存、重试、压缩、指标采集。
  • 误用风险:包装层过多会让调用链排障困难。
  • 边界辨析:它增强职责,但一般不改变“是否允许访问”。

Facade:把复杂性留在系统内部,而不是散落到每一个调用点

关键词:统一入口、编排收束、认知减负。

Facade 不是让复杂系统“变简单”,而是让复杂性待在该待的地方。下单、扣款、库存预占、通知、物流、审计这类协作经常一起出现。如果每个调用方都各自拼一遍流程,系统会快速出现重复编排和分叉逻辑。

门面的作用是把稳定协作流程集中收口,对外只暴露一个高层接口。这样你就能在一个地方统一处理异常、回滚、日志和顺序约束,而不是靠调用方自己“记得怎么调”。这是 AI 生成代码时代极其重要的结构闸门。

  • 变化点:子系统协作流程和对外入口数量。
  • 适合:下单、导出、聚合搜索、转码、支付编排。
  • 误用风险:Facade 很容易膨胀成无所不包的上帝对象。
  • 边界辨析:Facade 简化对外入口,Mediator 集中的是内部对象协作规则。

Flyweight:只有当对象数量和内存占用真的成为问题时,它才有意义

关键词:共享内部状态、海量对象、内存优化、外部化变化。

Flyweight 不主要解决业务抽象问题,而是解决资源成本问题。它假设大量细粒度对象之间存在可共享的不变部分,于是把这部分抽出来共享,把随上下文变化的那部分留给外部传入。

文本编辑器中的字形信息、地图里的图标元数据、棋盘格样式、粒子系统渲染参数都能体现这个思路。没有对象量级和性能压力时不要上它,因为它会把简单对象模型变得更难读、更难调试。

  • 变化点:内部不变状态与外部变化状态的切分方式。
  • 适合:字符字形、图标、粒子、规则元数据等高复用对象。
  • 误用风险:为了炫技过早做共享,结果把业务模型拆得支离破碎。
  • 边界辨析:它的主要收益是内存与对象数,而不是语义清晰度。

Proxy:接口看起来一样,但访问语义其实已经变了

关键词:访问控制、懒加载、远程性、权限边界。

Proxy 和 Decorator 的类图常常很像,但解决的问题完全不同。Proxy 关注的是“你能不能直接碰真实对象”,或者“真实对象是否应该现在就被实例化”。权限校验、远程调用、事务边界、缓存命中、懒加载,都是访问语义问题。

Spring AOP 的大量能力都建立在代理机制上;ORM 中的懒加载对象也经常是代理思想的实现。它让调用方面对一个稳定接口,但在真实访问前多了一层决策与控制。[6]

  • 变化点:访问前后需要附着的控制语义。
  • 适合:权限、远程服务、事务边界、懒加载、保护性包装。
  • 误用风险:把普通增强都做成代理,会让职责意图变模糊。
  • 边界辨析:Proxy 关注“能不能、何时、以何种方式访问”,不是“增加新职责”。

Chain of Responsibility:让“谁来处理这个请求”成为可配置的链,而不是写死的分支

关键词:请求链、逐步处理、短路、可插拔责任节点。

Chain of Responsibility 的核心是把“处理权”从调用点剥离出来。一个请求沿着责任链流动,每个节点都可以处理、修改、放行或终止。这样发送者不必认识最终处理者,也不必知道处理顺序的全部细节。

审批链、过滤器链、中间件链、风控链、告警处理链都非常适合它。真正的收益不是“看起来很优雅”,而是新增一个责任节点时,旧节点通常不用被改动。

  • 变化点:处理顺序、处理权归属、拦截点数量。
  • 适合:审批流、过滤器、中间件、规则链、插件式处理。
  • 误用风险:链太长且有副作用时,问题定位会很痛苦。
  • 边界辨析:它是“请求沿链流动”,不是“事件广播给所有人”。

Command:当动作本身需要被排队、记录、撤销或重放时,请把它对象化

关键词:请求对象化、撤销重做、任务排队、审计重放。

Command 的第一性问题是“这个动作是不是也要像数据一样被保存和传递”。如果一次操作需要进队列、做审计、支持撤销重做、组合成宏命令、跨线程或跨进程转交,那么普通函数调用就不够了,因为调用一旦发生就只剩结果。

把请求对象化后,调用者、接收者和执行时机就被彻底拆开。编辑器中的撤销重做、任务系统中的作业描述、工作流动作单元都体现了这种设计思路。

  • 变化点:执行时机、执行记录、撤销能力、目标接收者。
  • 适合:任务队列、撤销重做、批处理、宏命令、审计系统。
  • 误用风险:不会被保存或调度的简单调用,用 Command 只是加壳。
  • 边界辨析:Command 是“请求对象化”,Strategy 是“算法对象化”。

Interpreter:当业务规则开始像一门小语言时,才考虑为它建立语法与求值器

关键词:小型 DSL、语法规则、表达式求值、规则组合。

Interpreter 适合的不是“任意复杂规则”,而是规则规模已经超出简单配置和条件分支、但又还没大到需要完整编译器框架的那段区间。它把语法规则和求值逻辑都对象化,让规则能够被组合、嵌套和解释执行。

告警表达式、过滤条件、权限判断式、促销规则、小型查询语法都可能进入这个区域。它的价值在于把规则从代码 if-else 里抽出来,但代价是语法设计、错误提示和性能问题都会随之出现。

  • 变化点:语法组合方式与求值规则,而非单条规则内容。
  • 适合:小 DSL、规则表达式、查询语言、过滤器语法。
  • 误用风险:语法一复杂就会迅速走向“自己造半个编译器”的深坑。
  • 边界辨析:Interpreter 解决“规则像语言”,不是“规则很多”。

Iterator:把“如何遍历”从“集合内部怎么存”里分离出来

关键词:统一遍历协议、容器解耦、顺序访问、惰性消费。

Iterator 今天显得不那么“模式化”,是因为很多语言已经把它内建成标准协议。可它解决的问题并没有消失:调用方需要顺序访问元素,但不应该依赖集合的内部表示。否则每种容器都会带来一套不同的遍历 API。

Java 的 Iterator 接口和 Python 的迭代器协议,正是这个模式被语言与标准库吸收后的结果。学它的重点不是手写 next(),而是理解统一遍历协议为什么能大幅降低容器抽象的成本。[14][15]

  • 变化点:集合内部组织方式与遍历策略。
  • 适合:自定义容器、惰性序列、树遍历、批量消费管道。
  • 误用风险:为本来就能用标准迭代协议的场景再造一层自定义迭代器。
  • 边界辨析:Iterator 管“如何访问元素”,Composite 管“元素怎样组成树”。

Mediator:先砍掉对象之间的网状连线,再把协作规则集中到一个地方

关键词:协作中枢、网状耦合治理、交互收束。

Mediator 适合那种“每个对象都认识其他很多对象”的系统。表单联动、复杂 UI、聊天室、工作流节点协同,只要对象间交互一多,局部改动就会牵一发动全身。此时问题不是单个对象职责不清,而是协作规则散落在太多对象里。

通过引入中介者,你把原来 N 对 N 的互相引用变成每个参与者只依赖 mediator。这样协作规则被集中表达,参与者角色也更清晰。但如果所有逻辑都堆进 mediator,它又会反过来变成巨大的调度中心。

  • 变化点:对象协作规则与交互关系图。
  • 适合:复杂表单、聊天室、工作流编排节点、控制中心。
  • 误用风险:中介者变成“所有东西都来问我”的超级类。
  • 边界辨析:Mediator 集中内部协作规则,Facade 简化对外使用入口。

Memento:保存状态不是难点,难点是怎样在不破坏封装的前提下恢复它

关键词:快照、撤销点、恢复、封装保护。

Memento 的第一性问题是“状态恢复应否暴露内部细节”。如果一个对象需要支持撤销、回滚或快照,你当然可以把内部字段全抛出来给外部记,但那会直接破坏封装。Memento 让状态保存成为一个被允许的出口,却不要求外界理解对象内部结构。

编辑器文档、画布操作、局部会话状态、表单临时草稿都很常见。真正的难点在于明确哪些状态属于快照的一部分,哪些是派生值、外部资源或副作用。否则“恢复”只会恢复出一个看似相似、实际不一致的对象。

  • 变化点:可回滚状态边界和快照粒度。
  • 适合:撤销重做、草稿恢复、局部快照、时间点回看。
  • 误用风险:把外部副作用也想当然地纳入回滚,会产生虚假的一致性幻觉。
  • 边界辨析:Memento 保存的是状态,Command 保存的是动作。

Observer:事实发布出去,但发布方不该知道所有反应方是谁

关键词:发布订阅、事件传播、附加行为解耦。

Observer 最重要的思想是把“事情发生了”与“谁要响应”分开。订单支付成功后,积分、通知、审计、推荐更新、风控记录都可能要行动。如果主流程亲自调所有下游,每增加一个消费者都得回头修改主链路。

Spring 的 ApplicationEventPublisher 和 Django Signals 都是这种思想的经典工程化体现。它们不是为了炫耀解耦,而是让主流程只负责发布事实,把附加行为开放给订阅者自己接住。[5][8]

  • 变化点:响应者集合、订阅关系、扩展动作数量。
  • 适合:领域事件、通知系统、插件机制、扩展钩子。
  • 误用风险:把事件机制当成异步和一致性的万能药。
  • 边界辨析:Observer 是“一处变化多方响应”,Chain 是“一个请求依次流经多环”。

State:行为不是随机变化,而是被内部状态机驱动着变化

关键词:状态机、状态迁移、行为切换、条件爆炸治理。

State 模式真正要解决的是“状态分支已经开始侵蚀核心对象”。当一个对象在待支付、已支付、已发货、已取消等不同状态下允许的操作和行为都不同,如果你把这些逻辑都写在一个类里,最终一定会演变成巨大的条件判断矩阵。

State 把状态特有行为移到独立状态对象中,让上下文对象只负责持有当前状态和触发迁移。这样状态迁移规则、非法操作和状态特有副作用都能变得显式,也更容易测试。

  • 变化点:行为随内部状态变化而切换。
  • 适合:订单状态机、连接状态、工作流节点、游戏角色状态。
  • 误用风险:如果状态很少且基本不扩展,State 可能比普通分支更重。
  • 边界辨析:State 是“对象因内部状态不同而变”,Strategy 是“外部选择不同算法”。

Strategy:把变化算法从主流程里拔出来,而不是继续给主流程加分支

关键词:算法家族、可替换、运行时注入、条件泥潭治理。

Strategy 的本质,是承认“算法”本身就是变化点。折扣规则、路由策略、排序规则、推荐召回、风控评分,这些场景的目标一致,但实现规则常常变化。如果主流程中不断累加 if / elif / else,最终会形成又长又脆的条件泥潭。

AI Coding 场景里,这一点尤其重要。模型对“多加一个分支”极其顺手,但不会天然识别这些分支其实属于一组可替换算法。能否把这类变化从主流程里抽成策略接口,往往决定系统在第三、第四次迭代后是否还能继续演化。

  • 变化点:同一职责下的多种算法。
  • 适合:定价、排序、调度、风控、压缩、推荐、审核。
  • 误用风险:只有一个稳定实现时,抽策略只会增加跳转层。
  • 边界辨析:Strategy 变的是算法,Template Method 变的是骨架中的局部步骤。

Template Method:主流程骨架先固定,再把真正变化的步骤让出去

关键词:算法骨架、步骤延迟、流程稳定、继承钩子。

Template Method 适合那种“流程顺序稳定,但局部步骤可变”的问题。比如 ETL、数据库访问、导出流程、资源获取与释放。你不希望每个调用方都自己拼接顺序,也不希望把所有细节都写死在同一个实现里。

它通过父类固定流程骨架,再让子类实现个别步骤,从而确保关键顺序不被破坏。Spring 的 JdbcTemplate 就是经典工业案例:资源管理和主流程由模板兜住,调用方只提供真正变化的行为。[7]

  • 变化点:固定骨架中的局部步骤。
  • 适合:ETL、数据库模板、固定审批流程、统一资源管道。
  • 误用风险:继承层次过深时,很容易进入脆弱基类与继承地狱。
  • 边界辨析:Template Method 靠继承固化骨架,Strategy 靠组合替换算法。

Visitor:结构稳定时,把新操作持续从结构外侧长出来

关键词:操作外提、稳定结构、多操作扩展、双分派。

Visitor 的第一性问题是“到底是数据结构更稳定,还是操作集合更稳定”。如果节点结构相对稳定,但你要不断新增分析、导出、校验、统计、格式化等操作,那么把每个新操作都塞回节点类中,会让结构类不断膨胀。

Visitor 的解决方式是把操作从结构中抽走,新操作通过新增 visitor 来接入,而不必改每个节点类。AST、规则树、编译器节点、复杂报表对象图都非常适合这种模式。代价则是新增节点类型时,所有 visitor 都要跟着补。

  • 变化点:不断新增的操作集合,而不是节点结构本身。
  • 适合:AST、表达式树、规则引擎节点、报表对象图。
  • 误用风险:如果结构本身变得很快,Visitor 会让维护成本反向爆炸。
  • 边界辨析:Composite 解决“树怎么长”,Visitor 解决“对树做什么新操作”。

四、23 个模式放在一起时,应该怎么选

真正的难点从来不是“记不住名字”,而是“两个模式看起来都像能用,到底该上谁”。下表先给出问题到模式的映射,再给出最容易混淆的一组组边界。判断时优先看变化点,不要先看类图长得像不像。

真正的问题 优先考虑的模式 判断句
同一目标有多种算法Strategy变化的是算法,不是主流程骨架
流程顺序稳定,但步骤细节会变Template Method先固定骨架,再放开局部步骤
对象创建过程会变化Factory Method使用方不该知道具体实现如何被实例化
需要切换一整套兼容组件Abstract Factory你要切换的是对象家族,不是单个对象
对象构造非常复杂Builder构建过程本身已经值得被建模
复制现成对象比重建更划算Prototype初始化成本和默认配置比复制更重
旧接口和新接口不兼容Adapter先补翻译层,而不是大拆旧系统
两个维度同时变化Bridge先拆变化轴,再做组合
结构是树,部分与整体都要统一操作Composite调用方不应反复区分叶子和容器
横切增强需要可叠加Decorator增强职责,而不是造一堆组合子类
访问前要做权限、远程、懒加载控制Proxy接口不变,但访问语义改变
子系统太复杂,对外要统一入口Facade调用方不该承担底层编排
对象数量极大,内存成为主要问题Flyweight共享内部状态,外部传入变化部分
一个事件要通知多个响应者Observer发布方不应认识所有消费者
请求该由谁处理需要动态决定Chain of Responsibility让请求沿责任链流动
动作需要排队、审计、撤销、重放Command请求本身也要像数据一样被管理
对象行为随状态机切换State变化的是内部状态,不是外部算法选择
对象之间交互网状耦合过密Mediator先收拢协作规则,再谈局部职责
需要保存并恢复内部状态Memento恢复不能靠暴露内部字段来完成
需要为小型 DSL 定义规则与求值Interpreter规则开始像一门语言而不只是配置
需要统一遍历不同容器Iterator遍历协议不该依赖内部存储结构
结构稳定,但不断要加新操作Visitor把操作从节点类里抽出去
确实需要全局唯一实例Singleton但先证明这真是业务约束,不是偷懒
容易混淆的组合 本质差别 一句更准确的判断
Factory Method vs Abstract Factory一个管单对象创建,一个管对象族一致性只切一个产品时别上对象家族工厂
Builder vs Prototype一个强调构建步骤,一个强调从样板复制复杂在“怎么拼”还是复杂在“初始化太贵”
Adapter vs Decorator vs Proxy翻译接口、增强职责、控制访问先问意图,再看结构
Bridge vs Strategy两个正交变化轴 vs 一个算法位可替换类在按笛卡尔积膨胀时先想到 Bridge
Strategy vs State外部选择算法 vs 内部状态驱动行为有没有状态迁移图,是判断关键
Template Method vs Strategy继承固化骨架 vs 组合替换算法顺序是否必须被父类强约束
Facade vs Mediator对外简化入口 vs 对内集中协作规则调用者在系统外还是对象群内部
Composite vs Visitor统一树结构 vs 给稳定树加新操作变化的是结构还是操作集合
Command vs Memento保存动作 vs 保存状态撤销是靠重放反向动作还是恢复快照
Observer vs Chain of Responsibility广播给多个响应者 vs 顺序流经处理链一个事实是否应被多人同时感知

五、AI Coding 时代最常见的模式误用:不是“不会模式”,而是“太快地乱用模式”

当模型能在几十秒里生成大量代码时,模式误用的代价会被放大。最危险的情况往往不是没有模式,而是模型学会几个术语之后,到处套接口、套工厂、套代理,看起来很工程化,实际只是在制造更多认知层级。

误用信号 表面上看在做什么 实际后果 更稳妥的做法
只有一个实现,却先造接口和工厂“提前留扩展点”跳转层增多,定位更慢,扩展并未真实发生先直写实现,等变化出现再抽象
把所有共享资源都做成 Singleton“全局方便访问”测试污染、并发语义模糊、依赖隐藏优先 DI 容器与显式依赖传递
所有附加逻辑都发事件“彻底解耦”顺序不清、一致性松散、观测困难只有真正需要多方响应时再上 Observer
Decorator / Proxy / Adapter 混着用“反正都是包一层”设计意图模糊,排障时根本不知道边界在哪先回答这层是翻译、增强还是控权
用 Template Method 堆很深继承层级“抽出公共流程”脆弱基类、子类耦合、修改牵一发而动全身流程不稳定时优先组合与策略注入
把 Flyweight 用到普通业务对象上“提前优化内存”对象模型难懂,性能收益却不存在先有对象量级和瓶颈证据再做共享
把 Interpreter 用在规则稍复杂就上 DSL“更灵活”开始自己维护半门语言,错误提示与调试代价暴涨先看数据驱动配置是否已经足够
责任链没有日志和顺序约束“可插拔”链路行为不可见,副作用难定位显式声明顺序、短路语义和追踪信息
一个务实结论: 最好的设计模式使用方式,不是“模式越多越高级”,而是“只有在真实变化出现时,才精确地引入恰当模式”。抽象不是奖杯,抽象是必须自己持续付维护成本的债。

六、为什么现代框架反复长成这些模式

设计模式不是教科书里孤立的名词,而是主流框架长期演化后自然收敛出的结构。只要你开始理解框架为什么那样设计,就会发现它们不断重现的并不是“语法技巧”,而是相同的变化管理问题。[2][11][12]

框架/机制 背后的模式 真正值得理解的点
Spring Bean 创建与作用域Factory Method / Abstract Factory / Singleton对象创建与生命周期不应该散落在业务代码里。[4]
Spring AOPProxy横切控制和访问语义可以借代理层统一织入。[6]
Spring JdbcTemplateTemplate Method流程骨架与资源管理由模板兜住,变化行为通过回调注入。[7]
Django SignalsObserver发布事实与附加响应解耦,但一致性仍需单独设计。[8]
Flask Application FactoryFactory Method创建应用对象本身就是框架扩展点。[9]
Requests Transport AdapterAdapter网络传输层差异通过统一适配接口收口。[10]
Java Iterator / Python 迭代协议Iterator经典模式会被语言吸收进标准接口,但设计意图不会消失。[14][15]

这件事对 AI Coding 的意义非常直接:如果你不理解框架底层依赖的是哪类结构,模型就会为你生成“能跑,但不顺着框架哲学”的代码。短期可能能过,长期通常会在扩展、测试和维护阶段集中还债。

七、最后的判断:模式不是为了背诵,而是为了提前看见代码未来会怎么坏

设计模式在今天真正重要,不是因为它们属于经典,而是因为它们提供了一套跨语言、跨框架、跨时代都稳定有效的结构判断工具。 当 AI 让代码生成变得极快时,人类最值钱的能力就不再是“我能把第一版写出来”,而是“我知道这段代码在第五次迭代后会朝哪里腐化,以及该如何在一开始就把腐化路径切断”。[13][16]

所以,理解 GoF 23 种模式,并不是为了把系统写得更像教科书;而是为了让你在面对不断变化的需求、不断扩张的调用链、不断累积的技术债时,始终还能用一套稳定的语言描述问题、切开问题、验证问题。

一句收束全文的话: AI Coding 时代,真正稀缺的不是代码,而是结构判断。模式本身不是终点,但它们仍然是训练结构判断最短、最稳、最经得起时间检验的一组基准样本。

参考文献

[1] Gamma, E., Helm, R., Johnson, R., and Vlissides, J. Design Patterns: Elements of Reusable Object-Oriented Software 相关书目概述。

[2] Gamma, E., Helm, R., Johnson, R., and Vlissides, J. Design Patterns: 15 Years Later.

[3] Fowler, M. Inversion of Control Containers and the Dependency Injection Pattern.

[4] Spring Framework Reference. Bean Scopes.

[5] Spring Framework Javadoc. ApplicationEventPublisher.

[6] Spring Framework Reference. Proxying Mechanisms.

[7] Spring Framework Reference. JDBC Core and JdbcTemplate.

[8] Django Documentation. Signals.

[9] Flask Documentation. Application Factories.

[10] Requests Documentation. Transport Adapters.

[11] SourceMaking. Design Patterns Catalog.

[12] Refactoring.Guru. Design Patterns Catalog.

[13] Refactoring.Guru. The History of Design Patterns.

[14] Oracle Java SE 8 API. Iterator.

[15] Python Documentation. Iterator Types.

[16] Ampatzoglou, A., Chatzigeorgiou, A., Charalampidou, S., and Avgeriou, P. The effect of GoF design patterns on stability: A case study. Information and Software Technology, 2015.

[17] Ampatzoglou, A., et al. The state of the art on software engineering design patterns: A systematic mapping study. Journal of Systems and Software, 2016.