14275 字
38 分钟
blog随心博客
设计模式

被专家说了为了长远发展,自己的设计模式和操作系统部分需要提升,虽然我也不是软工的,但是设计模式应该是以后指挥AI学习的很重要的部分。

一、总览#

设计模式(Design Patterns)是软件工程中针对反复出现的问题总结出的通用解决方案。GoF(Gang of Four)在《设计模式:可复用面向对象软件的基础》一书中归纳了 23 种经典设计模式,分为三大类:

类型数量核心思路
创建型5 种将对象的创建与使用解耦
结构型7 种将类或对象组合成更大的结构
行为型11 种管理对象之间的交互与职责分配

对于 AI 开发者尤其重要:当你用 LangChain/LangGraph 构建 Agent 时,实际上在反复使用这些模式——比如 Chain 就是责任链,Tool 就是策略,Memory 常是备忘录。掌握设计模式,本质上是在掌握"如何清晰地指挥 AI 写代码"的能力

二、设计六大原则#

设计模式不是凭空发明的,它们都遵循以下六大原则。理解原则比背模式更重要——原则是"道",模式是"术"。

2.1 单一职责原则(Single Responsibility Principle, SRP)#

一个类只负责一件事。

为什么需要它:如果一个类承担了多个职责,任何一个职责的变更都可能影响其他职责,导致系统变得脆弱。

示例:一个 ReportGenerator 不应该既处理数据查询、又生成图表、又发送邮件。应该拆分为 DataFetcherChartRendererEmailSender

AI 开发启发:设计 Agent 的 Tool 时,每个 Tool 应该只做一件事。如果一个 Tool 的描述里出现了"和"字,考虑拆成两个。

2.2 开闭原则(Open-Closed Principle, OCP)#

对扩展开放,对修改关闭。

为什么需要它:修改已有代码有引入 bug 的风险。理想情况是新增需求时只添加新代码,不改旧代码。

示例:一个支付系统最初支持微信支付,后来要加支付宝。如果每次都要改 PaymentService 的 if-else,就违反 OCP。正确的做法是定义一个 PaymentMethod 接口,微信和支付宝各自实现它。

AI 开发启发:LangChain 1.0 的 AgentMiddleware 体系就是对 OCP 的实践——新增功能只需加一个新的 middleware 类(如 PIIMiddlewareSummarizationMiddleware),无需修改 Agent 核心的 ReAct 循环代码。

2.3 里氏替换原则(Liskov Substitution Principle, LSP)#

子类必须能够完全替换父类,而程序行为不变。

为什么需要它:如果子类不能替换父类,那么多态就失效了——使用父类的地方必须知道子类的特殊行为,破坏了封装。

示例:一个 Rectangle 类有 setWidthsetHeight。如果让 Square 继承 Rectangle,并在 setWidth 里同时修改高度,使用者调用 setWidth(5); setHeight(10) 时期望面积是 50,结果却是 100(因为 setHeight 把宽度也改了)。这就违反了 LSP。

AI 开发启发:自定义 Agent 继承基类时,不要偷偷改变基类方法的行为语义。如果 Agent 的 invoke 方法签名一样但行为截然不同,调用方会踩坑。

2.4 接口隔离原则(Interface Segregation Principle, ISP)#

不强迫使用者依赖它不需要的接口。

为什么需要它:胖接口会让实现类被迫实现无关方法,也会让调用方看到不该看到的方法。

示例:一个 Worker 接口同时包含 code()design()manage(),会让普通程序员被迫实现 manage() 方法(抛出 UnsupportedOperationException)。应该拆成 CoderDesignerManager 三个小接口。

AI 开发启发:定义 Tool 的 Schema 时,参数只暴露必需要素,不要为了方便把不相关的参数塞进同一个 Schema。

2.5 依赖倒置原则(Dependency Inversion Principle, DIP)#

高层模块不应依赖低层模块,两者都应依赖抽象。

为什么需要它:直接依赖会导致"牵一发而动全身"——底层数据库换了,上层业务逻辑也得跟着改。

示例UserService 不应该直接依赖 MySQLUserRepository,而应该依赖 UserRepository 接口。这样换数据库时只需提供新的实现。

AI 开发启发:Agent 依赖 LLM 时,通过抽象层(如 LangChain 的 BaseChatModel)而不是直接调用特定模型的 API(如 openai.ChatCompletion.create)。这样切换模型时 Agent 代码无需改动。

2.6 迪米特法则(Law of Demeter, LoD / 最少知识原则)#

一个对象应该对其他对象有尽可能少的了解。

为什么需要它:类与类之间关系越密切,耦合度越高。一个类的变化会影响关联的多个类。

示例a.getB().getC().doSomething() 是不好的——调用方需要知道 B 的存在、知道 B 有 C、知道 C 有 doSomething。应该改成 a.doSomething(),让 A 内部去协调。

AI 开发启发:在 LangGraph 中设计节点之间的消息传递时,每个节点只接收它需要的数据,不要传递整个 state 对象让节点自己去翻找。


三、23种设计模式#

0 总览对比表#

下表按类型列出全部 23 种 GoF 模式,帮助你快速建立全局认知:

类型模式一句话概括典型场景AI 开发相关度
创建型简单工厂一个工厂按参数创建不同产品根据配置创建不同 Logger⭐⭐⭐
创建型工厂方法子类决定创建哪个具体产品不同数据库的 Connection 创建⭐⭐⭐
创建型抽象工厂创建一组相关产品族跨平台 UI 组件创建⭐⭐
创建型建造者分步骤构造复杂对象构建复杂的 Prompt 模板⭐⭐⭐⭐
创建型单例全局唯一实例LLM 客户端复用⭐⭐⭐⭐⭐
创建型原型克隆已有对象而非 new复制配置模板快速生成变体⭐⭐
结构型适配器转换接口让不兼容的类协作统一不同 LLM 的 API 调用⭐⭐⭐⭐⭐
结构型桥接分离抽象与实现,各自独立变化分离 Prompt 结构与模型调用⭐⭐⭐
结构型组合树形结构,统一处理整体与部分嵌套 Chain / Agent 嵌套调用⭐⭐⭐⭐
结构型装饰动态给对象添加职责给 LLM 调用添加缓存/重试/日志⭐⭐⭐⭐⭐
结构型外观为复杂子系统提供统一入口封装多步 Agent 流程为单一接口⭐⭐⭐⭐
结构型享元共享细粒度对象减少内存共享 Embedding 缓存⭐⭐
结构型代理控制对对象的访问LLM 调用的限流和鉴权代理⭐⭐⭐⭐
行为型责任链请求沿链传递直到被处理Middleware 链式处理请求⭐⭐⭐⭐⭐
行为型命令将请求封装为对象Agent 的任务队列/撤销操作⭐⭐⭐⭐
行为型解释器定义语法并解释执行自定义 DSL / Prompt 模板引擎⭐⭐
行为型迭代器统一遍历方式,隐藏内部结构流式输出 Token / 分页读取数据⭐⭐⭐⭐
行为型中介者用中介对象协调多方交互多 Agent 协作的消息调度⭐⭐⭐⭐⭐
行为型备忘录保存和恢复对象状态Agent 对话状态快照与回滚⭐⭐⭐⭐⭐
行为型观察者一对多依赖,状态变化自动通知Streaming 回调 / 事件驱动 Agent⭐⭐⭐⭐⭐
行为型状态对象行为随内部状态改变多步骤 Agent 的状态机控制⭐⭐⭐⭐⭐
行为型策略封装可互换的算法族按场景选择不同 LLM 或 Prompt⭐⭐⭐⭐⭐
行为型模板方法定义算法骨架,子类填充细节定义 Agent 处理流程框架⭐⭐⭐⭐
行为型访问者在不改变元素类的前提下添加新操作对 AST / 文档结构做多种分析⭐⭐

注:简单工厂不在 GoF 23 种之内,但因实用性强,几乎所有教程都会先讲它。此外,表中的"AI 开发相关度"是基于 2024-2026 年 LangChain/LangGraph/Agent 开发实践的判断,供参考。


以下按类型分组详细介绍每种设计模式。


创建型模式(Creational Patterns)#

1. 简单工厂(Simple Factory)#

不是 GoF 23 种之一,却是最常用的入门模式。用一个工厂类根据参数创建不同类型的对象。

提出背景:在早期面向对象编程中,开发者经常在业务代码里到处写 new。当需要替换具体类时(比如把 MySQLConnection 换成 PostgresConnection),所有出现 new 的地方都要改。简单工厂将"创建"集中起来,让业务代码只依赖工厂而不依赖具体类。

核心思想:定义一个工厂类,它有一个静态方法,根据传入的参数(字符串或枚举)决定实例化哪个具体类。所有产品类实现同一个接口。

简单工厂模式

生活实例:快递站。你告诉快递员"寄到北京",快递员(工厂)根据目的地选择顺丰还是圆通(具体产品),你不需要知道快递公司的内部运作。

代码示意

Python3 点击展开代码
23 lines 展开代码

优缺点

优点缺点
将创建和使用分离,业务代码更干净每增加一个产品就要修改工厂类(违反 OCP)
集中管理创建逻辑,方便统一配置产品种类多时工厂会变成"超级 if-else"
入门简单,适合产品类型较少的场景工厂类本身会成为新的单一故障点

AI 开发应用:在 LangChain 中,ChatOpenAIChatAnthropic 等模型的创建本质上就是简单工厂。当你的应用需要根据配置文件切换 LLM provider 时,一个 LLMFactory 可以避免业务代码里散落的条件判断。但注意:当 provider 超过 5 个时,应升级为工厂方法或抽象工厂。


2. 工厂方法(Factory Method)#

定义一个创建对象的接口,但让子类决定实例化哪个类。将对象的创建延迟到子类。

提出背景:简单工厂的问题是——每次加新产品都要修改工厂类,违反了开闭原则。框架开发者面临一个困境:框架需要创建对象,但具体创建什么对象应该由框架使用者决定。工厂方法解决了这个问题。

核心思想:定义一个抽象的"创建者"类,它声明一个抽象的工厂方法。每个具体子类实现这个工厂方法来创建特定的产品。使用者通过继承来扩展,而不是修改原代码。

生活实例:连锁餐厅的菜品制作。总部定义了"做汉堡"的流程(抽象工厂方法),但北京店和成都店的配方不同(具体子类)——北京店可能偏咸,成都店偏辣。总部不需要知道辣度细节,每个分店自己决定。

代码示意

Python3 点击展开代码
19 lines 展开代码

优缺点

优点缺点
符合开闭原则,新增产品只需加子类类的数量成倍增加(每个产品都要一个工厂子类)
创建逻辑与业务逻辑解耦只适用于产品有统一接口的情况

AI 开发应用:非常适合"框架型"AI 应用。比如你开发一个 Agent 框架,框架定义了 create_tools 这个工厂方法,不同的 Agent 子类(代码 Agent、数据分析 Agent、客服 Agent)各自实现它来注册自己的工具。LangGraph 中自定义节点的创建也遵循类似的思路——框架定义了 Graph 的骨架,你通过"重写"节点来填充具体行为。


3. 抽象工厂(Abstract Factory)#

创建一组相关的产品对象,而不需要指定它们的具体类。

提出背景:当一个系统需要处理"产品族"时——比如一套 UI 组件在 Windows 和 Mac 上各有一套实现——工厂方法就力不从心了。你需要确保创建的按钮、文本框、下拉框都是同一风格的(不能 Windows 按钮配 Mac 下拉框)。

核心思想:定义一个抽象工厂接口,它包含多个工厂方法,每个方法创建产品族中的一种产品。具体工厂实现整个产品族。

生活实例:全屋定制家具。你有"现代简约"和"中式古典"两套方案(产品族),每套方案包含沙发、茶几、电视柜。你选了"现代简约"工厂,那它生产的所有家具风格一致,不会出现搭配混乱。

代码示意

Python3 点击展开代码
17 lines 展开代码

优缺点

优点缺点
保证产品族内的一致性增加新产品族中的产品类型很困难
隔离具体类的实现类的层级结构变得更复杂

AI 开发应用:当你需要同时兼容 OpenAI 原生 SDK 和 LangChain 的 Tool 体系时,抽象工厂可以确保你的 SearchTool + MemoryTool + CodeTool 全部来自同一体系,避免混用导致的接口不兼容。不过大部分场景下,适配器模式更实用,抽象工厂只在"多产品族 + 完整切换"的需求下才值得引入。


4. 建造者(Builder)#

将复杂对象的构造过程与它的表示分离,使得同样的构建过程可以创建不同的表示。

提出背景:当一个对象有十几个可选参数时,构造函数会变得臃肿且难以使用(new User(name, email, null, null, null, age, null, null, phone, address, null))。建造者模式通过"分步构建 + 最后组装"解决这个问题。

核心思想:定义一个 Builder,它提供一组方法逐步设置对象的各种属性,最后调用 build() 方法返回完整对象。

生活实例:点一杯定制咖啡。"中杯、脱脂奶、少糖、加一份浓缩"——你是逐步指定的(建造者),最后咖啡师一次性做出这杯咖啡(build)。

代码示意

Python3 点击展开代码
32 lines 展开代码

优缺点

优点缺点
参数设置清晰,链式调用可读性高需要多写一个 Builder 类
同一个构建过程可以复用对象不完整时可能提前 build
便于设置默认值和参数校验对于只有两三个参数的对象是过度设计

AI 开发应用:这是 AI 开发中最被低估的模式。构建复杂的 Prompt(system prompt + few-shot examples + context + user input)天然就是建造者模式。LangChain 的 ChatPromptTemplate.from_messages() 本质上就是一个 Builder。建议在你的项目中显式定义 PromptBuilder,让 Prompt 的组装逻辑清晰可测。


5. 单例模式(Singleton)#

确保一个类只有一个实例,并提供全局访问点。

提出背景:有些资源——如数据库连接池、配置管理器、日志记录器——在整个系统中只应该存在一份。多份实例可能造成资源浪费(多个连接池抢占连接)或状态不一致(两份配置各自被修改)。

核心思想:将构造函数私有化,通过一个静态方法提供实例。首次调用时创建实例并缓存,后续调用直接返回缓存。

单例模式示意图

生活实例:一个班级只有一个班主任。你不能"new 一个班主任",只能通过"找班主任"这个操作获取已有的那一个。不管谁来问,都是同一个人。

懒汉式 vs 饿汉式

  • 饿汉式:类加载时就创建实例。简单安全,但如果实例创建成本高且一直没用到,就浪费了。
  • 懒汉式:第一次调用 getInstance() 时才创建。按需加载,但需要注意线程安全问题(两个线程同时首次调用可能创建两个实例)。

代码示意

Python3 点击展开代码
25 lines 展开代码

优缺点

优点缺点
控制资源访问,避免重复创建全局状态隐式共享,增加耦合
全局访问点方便难以单元测试(不能 mock 成新实例)
可以延迟初始化可能隐藏依赖关系,使代码难以追踪

AI 开发应用:LLM 客户端的创建是单例模式的天然场景——建立连接开销大(加载模型配置、初始化 tokenizer),而且整个应用通常只有一个 LLM 连接。但要注意:如果你的应用需要同时连接多个模型,单例就变成了限制。此时应该用多例模式(一个模型一个实例的注册表)。


6. 原型模式(Prototype)#

通过拷贝已有对象来创建新对象,而不是调用构造函数。

提出背景:有些对象的创建成本极高——比如一个经过大量训练的模型配置对象,或者一个加载了全量数据的初始状态。重新 new + 一步步初始化太慢了,更高效的做法是复制一个已有对象然后微调。

核心思想:定义一个 clone() 方法,返回对象的一个副本。关键在于区分浅拷贝(复制引用)和深拷贝(复制整个对象图)。

原型模式

生活实例:复印机。你不会重新手写一份文件,而是把原件放进复印机,得到一份一模一样的副本,然后在上面做修改。

代码示意

Python3 点击展开代码
21 lines 展开代码

优缺点

优点缺点
创建成本低,避免昂贵的初始化深拷贝复杂对象图可能很棘手
可以动态生成对象变体clone 和构造函数职责可能重叠
运行时灵活增减属性循环引用的对象难以深拷贝

AI 开发应用:在 Agent 开发中,你经常需要一组"配置相似但行为不同"的 Agent 实例——比如多个代码审查 Agent,只有 system prompt 不同。用原型模式:创建一个 base_agent 配置作为原型,然后 clone + 微调。LangChain 1.0 中,create_agent(model=..., middleware=[...]) 的配置字典可以作为原型,通过 {**base_config, "middleware": [...extra...]} 快速生成变体。


结构型模式(Structural Patterns)#

7. 适配器模式(Adapter)#

将一个类的接口转换成客户端期望的另一个接口,让原本不兼容的类可以协作

提出背景:引入第三方库、遗留代码、或不同服务商 API 时,它们的接口往往与你的系统不兼容。你不可能为了迁就新库而重写整个系统,也不能强迫所有第三方遵循你的接口规范。

核心思想:创建一个适配器类,它实现客户端期望的接口,内部将调用委托给被适配的对象,完成数据格式和调用方式的转换。

适配器模式

生活实例:出国旅行用的电源转换插头。中国的插头(客户端期望接口)插不进英国的插座(被适配者),转换插头(适配器)在中间做转换,两头都对得上。

代码示意

Python3 点击展开代码
32 lines 展开代码

优缺点

优点缺点
让不兼容的接口协作,复用已有代码引入额外一层,轻微增加复杂度
符合单一职责原则(适配逻辑集中)适配器过多时系统变得零碎
对客户端透明,无需修改已有代码不支持被适配者的所有功能(最小公分母)

AI 开发应用这是 AI 开发中使用频率最高的设计模式。 不同 LLM 提供商的 API 各不相同——OpenAI 的 ChatCompletion.create、Anthropic 的 Messages.create、Google 的 GenerativeModel.generate_content——通过适配器可以将它们统一为一个 generate(messages) -> str 接口。你在 LangChain 中用的 BaseChatModel 本质上就是一套适配器体系。当你想引入一个新模型而 LangChain 还不支持时,自己写一个适配器是最合理的做法。


8. 桥接模式(Bridge)#

抽象实现分离,使它们可以独立变化。

提出背景:当一个类在两个维度上独立变化时——比如"提示词类型"和"模型类型"——如果使用继承,会产生笛卡尔积爆炸(3 种提示词 × 3 种模型 = 9 个子类)。桥接模式用组合替代继承,让两个维度各自独立扩展。

核心思想:定义一个抽象(持有实现接口的引用),实现接口有多个具体实现。抽象的变化通过组合实现的变化来完成,而非通过继承。

桥接模式

生活实例:遥控器(抽象)与电视品牌(实现)。你有一个万能遥控器,它不关心电视是什么品牌——索尼、三星、小米都可以。同样,遥控器本身可以升级(加语音功能),不影响电视的正常使用。两者独立变化。

代码示意

Python3 点击展开代码
35 lines 展开代码

优缺点

优点缺点
两个维度独立扩展,避免类爆炸增加系统的理解和设计难度
实现细节对客户端隐藏需要正确识别出独立变化的维度
运行时可以动态切换实现如果只有一个维度变化,就是过度设计

AI 开发应用:在 Prompt Engineering 中,Prompt 结构(zero-shot / few-shot / chain-of-thought)和 LLM 后端是两个独立变化的维度。桥接模式让你可以任意组合而不产生子类爆炸。另一个场景是:Agent 的推理策略(ReAct / Plan-and-Execute / Tree-of-Thought)作为抽象,具体的 LLM 作为实现,通过桥接自由搭配。


9. 组合模式(Composite)#

将对象组织为树形结构,使客户端可以统一处理单个对象和组合对象。

提出背景:在树形结构中(文件系统、组织架构、UI 组件树),客户端经常需要区分"叶子节点"和"分支节点",导致大量 if-else 判断。组合模式让叶子节点和分支节点实现同一个接口。

核心思想:定义一个抽象组件,叶子节点和组合节点都实现它。组合节点内部持有一组子组件,调用组合节点的方法时,它会递归调用所有子节点的方法。

生活实例:军队的指挥结构。一个师长下达"进攻"命令,他不需要区分下属是"直接作战的士兵"(叶子)还是"下辖多个团的旅长"(组合)——每个人都知道进攻是什么意思。旅长接到命令后,再递归地传达给下属。

代码示意

Python3 点击展开代码
37 lines 展开代码

优缺点

优点缺点
客户端代码简洁,不需要区分叶子和分支限制叶子节点和分支节点必须有相同的接口签名
方便添加新的组件类型过度抽象可能让系统更难理解
天然支持递归和嵌套类型安全问题(叶子节点添加子节点会抛异常)

AI 开发应用:在 LangGraph 的 StateGraph 中,一个 Node 既可以是简单的 LLM 调用(叶子),也可以内部嵌套一个完整的子 Graph(组合),它们都通过 add_node() 以统一的方式接入主图。LangChain 1.0 的 AgentMiddleware 列表也体现了组合模式——middleware=[A, B, C] 既可以放单个 middleware(叶子),也可以放一个 MiddlewareStack(组合),外部调用方式完全一致。


10. 装饰模式(Decorator)#

动态地给对象添加额外的职责,而不影响同一接口的其他对象。

提出背景:继承是实现功能扩展的最直接手段,但继承是静态的(编译时决定),而且会导致类的组合爆炸。你需要一种更灵活的方式——在运行时给对象"包上"新功能,而且可以多层包裹。

核心思想:定义一个装饰器类,它和被装饰对象实现相同的接口。装饰器内部持有一个被装饰对象的引用,在调用被装饰对象前后加入自己的逻辑。

生活实例:点外卖。"一个汉堡"是基础对象。你加了"多加芝士"就是一层装饰,又加了"不要生菜"是第二层装饰。每层装饰都返回"一个汉堡",但你得到的是一个层层包裹的汉堡。装饰的顺序可以自由组合。

代码示意

Python3 点击展开代码
56 lines 展开代码

优缺点

优点缺点
比继承更灵活,动态组合功能会创建很多小对象
每个装饰器职责单一,符合 SRP装饰顺序可能影响结果
装饰器可以独立测试调试时调用栈较深

AI 开发应用:LangChain 1.0 的 AgentMiddleware 体系——ModelRetryMiddlewareModelFallbackMiddlewareModelCallLimitMiddleware——就是装饰器模式的集大成。每个 middleware 只做一件横切事(重试、降级、限流),通过 create_agent(middleware=[...]) 灵活组合。在自己的项目中,建议用同样的思路:将 LLM 调用的缓存、重试、日志、限流分别封装成可组合的装饰层,而不是把所有逻辑塞进一个巨大的 call_llm 函数。


11. 外观模式(Facade)#

为子系统中的一组接口提供一个统一的高层接口,让子系统更容易使用。

提出背景:一个复杂的子系统可能有几十个类和方法。客户端只想要一个简单的结果(比如"分析这份数据"),不想关心内部是先查数据库还是先调 API、用哪个模型、怎么拼接结果。

核心思想:定义一个外观类,它封装了子系统的复杂性,对外只暴露几个简单的方法。客户端只依赖外观,不直接依赖子系统。

外观模式

生活实例:一键启动汽车。你按下启动键(外观),背后的子系统——电瓶供电、启动机转动、油泵供油、点火线圈点火——全部自动协调执行。你不需要手动去做每一步。

代码示意

Python3 点击展开代码
30 lines 展开代码

优缺点

优点缺点
简化客户端,降低学习成本外观可能变成"上帝类"
解耦——子系统变化不影响客户端高级用户可能失去对子系统的精细控制
提供系统的"入口点",层次清晰如果不主动暴露子系统,灵活性受损

AI 开发应用:一个完整的 AI 应用通常包含:LLM 调用 + 向量检索 + 数据库查询 + 工具调用 + 结果后处理。外观模式将这一整套流程封装为一个 analyze()chat() 方法。LangChain 1.0 的 create_agent() 就是外观模式——内部组合了 ReAct 循环、middleware 链、工具注册表、模型调用,对外只是一个 agent.invoke() 方法。


12. 享元模式(Flyweight)#

通过共享来高效支持大量细粒度对象,节省内存。

提出背景:当需要创建大量相似的对象时(比如一个文字编辑器中的每个字符都是一个对象),内存会迅速耗尽。享元模式将对象的内部状态(不变部分)和外部状态(可变部分)分离,内部状态可以被共享。

核心思想:创建享元工厂来管理共享对象池。请求对象时,如果池中已有相同内部状态的对象,直接返回它;否则创建新对象并加入池。

享元模式

生活实例:图书馆的书籍管理系统。每本《三体》的元数据(作者、ISBN、简介)只有一份——这是共享的内部状态。而每本实体书的借阅状态、所在书架——这是外部状态,每个副本不同。

代码示意

Python3 点击展开代码
20 lines 展开代码

优缺点

优点缺点
显著减少内存使用需要严格区分内部/外部状态
共享的对象越多,节省越明显代码复杂度上升
天然适合配合工厂模式使用引入共享可能导致线程安全问题

AI 开发应用:最典型的场景是 Embedding 缓存——同一段文本可能被多次编码(比如知识库中重复的段落),享元模式避免重复调用昂贵的 Embedding API。另一个场景是 LLM 的 KV Cache 共享——对于相同的 system prompt,多个请求可以共享初始部分的 KV Cache,减少重复计算。不过要注意:享元模式适用于读多写少、内部状态不变的场景。


13. 代理模式(Proxy)#

为另一个对象提供一个替身或占位符来控制对它的访问。

提出背景:有些对象创建成本高(大图片)、访问受限(敏感数据)、或位于远程(网络对象)。你不想直接暴露它,也不想每次访问都创建它。代理提供一个"中间人",根据需求延迟创建、控制权限、或转发远程调用。

核心思想:代理类和真实类实现同一个接口。代理持有对真实对象的引用,在调用真实对象前后可以执行额外逻辑(权限检查、日志、延迟加载等)。

代理模式示意图

生活实例:信用卡是银行账户的代理。你去商场刷卡(使用代理),不用带现金(真实对象——你的钱在银行)。信用卡在处理支付时可能会先检查余额(权限控制),记录交易日志(日志记录),月底再告诉你总花费。

代码示意

Python3 点击展开代码
39 lines 展开代码

优缺点

优点缺点
职责清晰——每个代理只关心一个横切关注点增加间接层,调用链变长
对客户端透明,无需修改使用代码代理过多时调试困难
代理可灵活组合如果全部功能都走代理,性能有轻微损耗

AI 开发应用:LangChain 1.0 的 wrap_model_call / wrap_tool_call 两个 Hook 就是代理模式——middleware 在模型/工具调用前后插入逻辑(限流、日志、缓存),对调用双方完全透明。ModelCallLimitMiddleware(限流代理)、AnthropicPromptCachingMiddleware(缓存代理)都是代理模式的直接应用。


行为型模式(Behavioral Patterns)#

14. 责任链模式(Chain of Responsibility)#

将多个处理对象连成一条链,请求沿链传递,直到某个对象处理它为止。

提出背景:当一个请求可能被多个处理器中的某一个处理,且处理顺序可变时,用 if-else 把所有处理器写死会导致代码臃肿且难以扩展。

核心思想:每个处理器持有下一个处理器的引用。接到请求后,自己能处理就处理,不能处理就传递给下一个。就像击鼓传花。

责任链模式

生活实例:请假审批流程。你提交请假申请 → 直属领导能批 3 天以内的 → 部门经理能批 7 天以内的 → 总监能批 15 天以内的 → CEO 能批无限天。每个审批人如果不能处理就向上传递。

代码示意

Python3 点击展开代码
42 lines 展开代码

优缺点

优点缺点
请求和处理者解耦请求可能走到链尾都没被处理
动态调整链的顺序和组成链太长会影响性能
符合单一职责原则调试时需要追踪整条链

AI 开发应用:LangChain 1.0 的 middleware 列表 [AuthMiddleware(), PIIMiddleware(), SummarizationMiddleware()] 就是一条责任链——请求按顺序经过每个 middleware,每个都可以处理、修改或阻断。此外,在 Agent 开发中,"工具选择链"也体现责任链思想——一个请求来了,先让代码工具尝试处理,不行再给搜索工具,再不行交给通用对话工具。


15. 命令模式(Command)#

请求封装为对象,从而可以用不同的请求参数化客户端、支持请求队列和日志记录、以及支持撤销操作。

提出背景:在 GUI 开发中,同一个操作(如"保存")可能由菜单项、工具栏按钮、快捷键触发。如果把逻辑直接写在事件处理器里,会四处重复。命令模式将"动作"封装为独立对象,可以在不同上下文中重用。

核心思想:定义一个命令接口(含 execute()undo() 方法),具体命令封装一个操作及其参数。调用者只依赖命令接口,不关心具体操作。

命令模式

生活实例:餐厅点菜。你把要点的菜写在订单上(命令对象),服务员(调用者)收集订单交给厨房(接收者),厨房按订单做菜。关键是——订单是独立的对象,可以被排队(多个订单)、可以被撤销(退菜)、可以被记录(日结对账)。

代码示意

Python3 点击展开代码
52 lines 展开代码

优缺点

优点缺点
将请求者和执行者完全解耦每个命令一个类,类数量膨胀
方便实现撤销/重做命令的 undo 实现可能很困难
支持命令队列、日志、事务简单场景下是过度设计

AI 开发应用:Agent 的工具调用天然适合命令模式。当 Agent 决定调用多个工具时,把每个工具调用封装为 ToolCallCommand,放入队列顺序执行。好处是:可以加入撤销逻辑(比如写文件后发现不对就删掉)、可以记录完整的调用日志用于审计、可以实现"重试"(重新执行整个命令队列)。在 LangGraph 中,Command 对象本身就是命令模式的体现——它封装了状态更新和目标路由。


16. 解释器模式(Interpreter)#

给定一种语言,定义它的文法表示,并定义一个解释器来解释执行这种语言中的句子。

提出背景:当程序需要处理特定领域的小型语言(DSL)时——如 SQL 查询、正则表达式、配置文件中的条件表达式——硬编码解析逻辑会变得难以维护。

核心思想:将文法中的每类语法规则映射为一个表达式类。要解释一个句子时,先解析为抽象语法树(AST),然后递归解释每个节点。

解释器模式

生活实例:计算器。你输入 1 + 2 * 3,计算器内部先解析这个表达式为一棵树(乘法节点下面有 2 和 3,加法节点下面有 1 和乘法结果),然后递归计算每个节点得到最终结果 7。

代码示意

Python3 点击展开代码
40 lines 展开代码

优缺点

优点缺点
语法规则易于扩展复杂文法会导致类爆炸
每类规则独立,符合 SRP不适合解析大型语言
可以组合规则执行效率不高

AI 开发应用:在 Prompt 工程中,自定义模板引擎(如 Jinja2 风格的 Prompt 模板)本质上用了解释器模式。LangChain 的 PromptTemplate{variable} 插值就是一个简化版的解释器。更高级的应用包括:定义 Agent 的"思考语言"(如 ReAct 的 Thought: ... Action: ... Observation: ...),用解释器模式解析和验证 Agent 的输出格式。

⚠️ 注意:在实际项目中,解释器模式很少自己从头实现。大多数情况下会使用现成的模板引擎、配置解析库或编译器工具。但理解它的原理有助于你设计自己的 Prompt DSL 或 Agent 指令格式。


17. 迭代器模式(Iterator)#

提供一种方法顺序访问聚合对象中的元素,而不暴露其内部表示。

提出背景:数据可能存储在各种结构中——列表、树、图、数据库游标——如果每种结构的遍历方式都不同,使用者就必须了解每种结构的遍历细节。迭代器提供一个统一的 next() / hasNext() 接口。

核心思想:将遍历行为从聚合对象中分离出来,封装在一个独立的迭代器对象中。聚合对象只负责返回迭代器。

迭代器模式示意图

生活实例:听歌时的播放列表。不管你的歌曲是存在本地硬盘、云盘、还是 U 盘里,播放器只需要知道"下一首"和"有没有下一首"——底层的存储结构对你完全透明。

代码示意

Python3 点击展开代码
20 lines 展开代码

优缺点

优点缺点
统一遍历接口,客户端代码简洁简单遍历(如列表)不需要迭代器
一个聚合可以有多个迭代器某些实现比直接索引遍历慢
可以暂停、恢复遍历遍历期间修改聚合可能导致问题

AI 开发应用:LLM 的流式输出(Streaming) 就是迭代器模式——模型逐个生成 token,你逐个消费,不需要知道 tokens 是怎么存储的。在设计 RAG 系统的文档检索器时,迭代器模式也很实用:不管底层是向量数据库、全文检索还是混合检索,对外都暴露统一的 search(query) -> Iterator[Document]


18. 中介者模式(Mediator)#

用一个中介对象封装一组对象的交互,避免这些对象之间显式地相互引用。

提出背景:在一个复杂的交互网络中(如聊天室、机场塔台、多 Agent 协作系统),如果让每个对象直接与其他对象通信,会形成网状依赖——N 个对象需要 O(N²) 个连接。中介者将所有通信集中到一点。

核心思想:引入一个中介者对象,所有通信都经过它。各组件只依赖中介者,不依赖其他组件。

生活实例:机场塔台。几十架飞机如果可以互相喊话协调降落顺序,场面会混乱。实际上所有飞机只和塔台(中介者)通信,塔台统一调度。每架飞机不需要知道其他飞机的存在。

代码示意

Python3 点击展开代码
40 lines 展开代码

优缺点

优点缺点
将网状依赖变成星形,降低耦合中介者容易变成"上帝对象"
各组件的修改相互独立中介者故障会导致整个系统瘫痪
集中控制交互逻辑,方便监控复杂的交互规则让中介者难以维护

AI 开发应用多 Agent 协作是中介者模式最核心的 AI 应用场景。 在 LangGraph 的多 Agent 系统中,图的 State 对象天然充当中介者——Agent A 把结果写入 State,Agent B 从 State 读取,Agent A 和 B 之间没有直接依赖。这种设计让你可以随意增删 Agent 而无需修改已有 Agent 的代码。AutoGen、CrewAI 等 Agent 框架的控制中心也都是中介者。


19. 备忘录模式(Memento)#

在不破坏封装的前提下,捕获并保存一个对象的内部状态,以便之后可以恢复到这个状态。

提出背景:很多场景需要"撤销"功能——文本编辑器的 Ctrl+Z、数据库的事务回滚、游戏的存档读档。问题在于:对象的状态是私有的,外部不应该直接读取,但你又要保存它。

核心思想:由原发器(Originator)自己创建一个备忘录对象(Memento)来保存当前状态。只有原发器可以访问备忘录的内容。外部通过管理者(Caretaker)持有备忘录,但不能查看或修改它。

备忘录模式示意图

生活实例:游戏存档。游戏角色(Originator)的状态——等级、装备、位置——被保存为一个存档文件(Memento)。你可以把存档文件存在文件夹里(Caretaker),但存档文件是加密的,你无法直接修改。要恢复时,游戏读取存档恢复到保存时的状态。

代码示意

Python3 点击展开代码
60 lines 展开代码

优缺点

优点缺点
不破坏封装即可保存和恢复状态状态数据大时,备忘录会占用大量内存
简化了原发器的撤销逻辑管理者需要知道何时创建快照
可以作为"事件溯源"的基础频繁保存导致性能开销

AI 开发应用:AI Agent 中的对话状态管理与备忘录模式完美契合。每次工具调用或对话轮次前,你都可以创建当前 Agent 状态的快照。当 Agent 走入死胡同时(工具调用陷入循环、产生错误结果),你可以回滚到之前的状态,换一种策略重试。LangGraph 中的 checkpointer 本质上就是备忘录的自动版本——每次节点执行后自动保存 State 快照,支持时间旅行调试和故障恢复。


20. 观察者模式(Observer)#

定义对象之间的一对多依赖关系。当一个对象(主题)状态改变时,所有依赖它的对象(观察者)都得到通知并自动更新。

提出背景:在很多系统中,一个事件发生后需要触发多个响应。比如用户下单后,需要扣库存、发邮件、记日志、推送通知。如果把这些逻辑全写在下单方法里,下单模块就太臃肿了。

核心思想:主题维护一个观察者列表。当状态变化时,主题遍历列表通知每个观察者。观察者可以随时订阅或取消订阅。

观察者模式示意图

生活实例:公众号订阅。你关注了一个公众号(订阅),公众号发布新文章时(状态变化),你和所有其他关注者都会收到推送(通知)。你随时可以取消关注。

代码示意

Python3 点击展开代码
59 lines 展开代码

优缺点

优点缺点
主题和观察者松耦合观察者过多时通知开销大
动态增删观察者,灵活观察者的执行顺序不可控
支持广播,符合开闭原则观察者中的错误可能影响主题

AI 开发应用:LLM 的流式输出(Streaming)是观察者模式的绝佳示例。LangChain 中的 BaseCallbackHandler 就是观察者——它监听 on_llm_starton_llm_new_tokenon_llm_end 等事件。你可以注册多个 Callback:一个在终端显示进度、一个记录 token 消耗、一个记录延迟到监控系统、一个在出错时发告警。它们彼此独立,互不干扰。


21. 状态模式(State)#

允许对象在其内部状态改变时改变它的行为,看起来就像对象的类发生了变化。

提出背景:当一个对象的行为严重依赖于它的状态时(如订单的待支付→已支付→已发货→已完成),代码中会出现大量的 if-else 或 switch 判断不同状态下的行为。每增加一个状态,所有相关方法都要修改。

核心思想:将每种状态封装为一个类,所有状态类实现同一个接口。上下文对象持有一个当前状态对象的引用,将行为委托给当前状态。切换状态就是替换状态对象。

生活实例:自动饮水机的出水控制。当水杯不在时(待机状态),按什么键都不出水。当水杯放入(就绪状态),按冷水键出冷水,按热水键出热水。正在出水时(出水状态),再按键可能停止出水。同一个按键,在不同状态下行为完全不同。

代码示意

Python3 点击展开代码
50 lines 展开代码

优缺点

优点缺点
消除大量 if-else,符合 OCP状态类数量随状态增多
状态转换逻辑清晰状态过多时维护状态转换矩阵困难
每个状态独立测试如果状态很少就是过度设计

AI 开发应用状态模式是 LangGraph 的根基。 Agent 的思考过程天然是状态机:等待用户输入 → 思考 → 调用工具 → 等待工具返回 → 生成回复 → 等待用户输入。LangGraph 的节点(Node)就是状态,边(Edge)就是状态转换。你定义一个 StateGraph,本质上就是在声明状态模式。理解状态模式 = 理解 LangGraph 为什么这样设计。


22. 策略模式(Strategy)#

定义一组算法,将它们各自封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用它的客户端。

提出背景:当你有一个任务可以用多种方式完成时——比如排序(快排、归并、冒泡)、支付(微信、支付宝、银行卡)、压缩(zip、rar、7z)——如果用 if-else,代码会膨胀且难以扩展。

核心思想:将每种算法封装为一个策略类,它们实现同一个策略接口。上下文持有一个策略引用,执行时委托给当前策略。策略可以在运行时切换。

策略类示意图

生活实例:导航软件。你要从 A 到 B,导航给出三条路线:最快路线、最短路线、避开高速。这三条路线就是三种策略——目标相同(从 A 到 B),算法不同。你可以在界面上随时切换。

代码示意

Python3 点击展开代码
45 lines 展开代码

优缺点

优点缺点
算法可以独立变化和测试客户端需要了解策略之间的区别以做出选择
避免 if-else,符合 OCP策略类数量随算法数量增多
运行时灵活切换大部分策略需要相同的参数签名

AI 开发应用:策略模式是 Prompt Engineering 的理论基础——Zero-shot、Few-shot、Chain-of-Thought、Tree-of-Thought 都是策略。LangChain 1.0 的 LLMToolSelectorMiddleware 本质上是策略模式:用轻量 LLM 预筛选工具(策略 A)还是直接给全部工具列表(策略 B),可以根据任务复杂度动态切换。在 Agent 开发中,让 Agent 自己决定用哪种推理策略是"自适应 Agent"的核心思路。


23. 模板方法模式(Template Method)#

在一个方法中定义算法的骨架(模板),将某些步骤延迟到子类中实现。子类可以在不改变算法结构的前提下,重新定义某些步骤。

提出背景:当多个类有相似的处理流程,但某些步骤的实现不同时——比如"打开文件 → 读取内容 → 处理数据 → 保存结果"这个流程,处理数据的步骤可能各不相同——应该提取不变的骨架,将变化的步骤交给子类。

核心思想:在父类的模板方法中(通常用 final 修饰)定义算法步骤的调用顺序。将可变步骤声明为抽象方法,由子类实现。

模板方法

生活实例:泡茶和泡咖啡。流程完全一样:烧水 → 冲泡 → 倒入杯中 → 加调料。区别只是"冲泡"这一步(茶叶 vs 咖啡粉)和"加调料"这一步(柠檬 vs 糖奶)。模板方法就是提取出"烧水 → X → 倒入杯中 → Y"这个骨架。

代码示意

Python3 点击展开代码
71 lines 展开代码

优缺点

优点缺点
代码复用——算法骨架只写一次受限于固定的算法结构
子类只关注差异部分模板方法可能变得过于复杂
符合"好莱坞原则":不要调用我,我会调用你继承的局限性——不如策略模式灵活

AI 开发应用:Agent 的处理流程天然适合模板方法。几乎所有 Agent 都遵循相似的模式:接收输入 → 理解意图 → 可能检索 → 可能调用工具 → 生成回复。将这套骨架定义在 AgentPipeline.run() 模板方法中,不同的 Agent(RAG Agent、代码 Agent、数据分析 Agent)只需实现其中差异化的步骤。LangGraph 的 StateGraph 本质上让这种模式从"编译时继承"升级为"运行时组合"——节点是步骤,边是流程控制。


24. 访问者模式(Visitor)#

表示一个作用于某对象结构中各元素的操作。访问者模式让你可以在不改变元素类的前提下,定义作用于这些元素的新操作。

提出背景:当你有一个相对稳定的对象结构(如 AST、文档树),但需要频繁地添加新的操作(如语法检查、格式化、代码生成),每次添加操作都去修改元素类会违反 OCP。

核心思想:元素类提供一个 accept(visitor) 方法,访问者类为每种元素类型提供一个 visit 重载。通过"双重分派"机制——客户端调用 element.accept(visitor),元素内部调用 visitor.visit(this)——找到对应的方法。

访问者模式示意图

生活实例:房屋验收。房子(对象结构)有客厅、厨房、卧室——它们是稳定的,不会轻易改变。验收员(访问者)逐一检查每个房间。不同的验收员做不同的事情:电工检查电路、水工检查管道、结构工程师检查墙体——你不需要为了增加新的验收项目而修改房间本身。

代码示意

Python3 点击展开代码
62 lines 展开代码

优缺点

优点缺点
添加新操作容易,不需改动元素类添加新元素类型需要修改所有访问者
相关操作集中在一个类中访问者需要访问元素的内部细节
访问者可以跨元素类累积状态双重分派机制较难理解

AI 开发应用:访问者模式在 AI 开发中使用较少,但在特定场景很强大。最典型的是文档处理:你的 Agent 需要处理混合文档(文字、表格、代码块、图片),不同的操作(生成摘要、提取代码、翻译文本、统计 token)都可以用访问者模式实现,而不需要修改文档元素的定义。另一个场景是 LangChain 的 Output Parser 体系——不同类型的输出(JSON、XML、Pydantic 对象)需要不同的解析策略,访问者模式可以优雅地管理这些解析逻辑。


四、AI 开发场景速查#

作为 AI 开发者,你可能不需要记住全部 23 种模式。以下是实际开发中最常用的场景映射:

你在做什么…对应模式一句话提示
对接多个 LLM 提供商适配器统一接口,各自适配
给 LLM 调用加缓存/重试/日志/审批装饰器 / Middleware层层包裹,自由组合(LangChain 1.0 AgentMiddleware)
设计 Middleware 链 / Agent 的请求处理拦截器链 + 责任链Middleware 顺序执行,各自做前置/后置
AgentMiddleware 的 Hook 扩展点模板方法ReAct 骨架固定,Hook 点留给 middleware 扩展
构建复杂的 System Prompt建造者分步设置,最后组装
Agent 在不同阶段做不同事状态每种状态一个类
切换 Prompt 写作方式策略zero-shot / few-shot / CoT
多 Agent 协作通信中介者所有消息经过中心调度
保存和回滚 Agent 状态备忘录快照对话,支持撤销
监听 LLM 流式输出观察者注册回调,自动通知
复用 LLM 连接避免重复创建单例全局唯一实例
统一多个子系统的调用入口外观封装复杂性,暴露简单接口
定义 Agent 处理流程框架模板方法固定骨架,子类填细节

五、学习建议#

  1. 先理解六大原则。模式是"术",原则是"道"。理解了原则,你甚至可以自己"发明"出还没被命名的模式。
  2. 不要为了用而用。代码中如果只有一个 if-else,引入策略模式就是过度设计。模式是复杂度的解药,不是调味料。
  3. 从 AI 开发相关度高的开始。适配器、装饰器、策略、观察者、责任链、模板方法——这六个模式在 LangChain 1.0 的 AgentMiddleware 和 LangGraph 中随处可见。其中 Middleware 体系本质上是拦截器链 + 模板方法的复合——ReAct 循环是骨架(模板方法),六个 Hook 是扩展点,middleware 链顺序拦截每个阶段(拦截器链)。掌握这些就能读懂 LangChain 源码的核心设计。
  4. 写代码时遇到问题再回头查。不要试图一次性记住 23 种模式。遇到"这个 if-else 太多怎么办"或"怎么在两个 LLM 间切换"时,回来查这张表。

专题阅读

blog

这篇文章属于同一条阅读链。你可以直接在这里切换,不用再回到列表页重新找。

当前进度1 / 5

阅读导航

文章目录

当前阅读位置将在这里显示

0 节