组合优于继承:Agent 协作中的设计智慧
“在软件设计中,继承是白盒复用,组合是黑盒复用。白盒让你看到太多你不该关心的细节,黑盒让你只关注你需要的接口。” —— GoF《设计模式》
TL;DR
本文核心观点:
- 强耦合与脆弱性 — 继承带来静态绑定、脆弱基类、违反封装等固有问题,任何对基类的修改都可能动摇整个继承树。
- 组合的优势 — 组合通过 has-a 关系实现松耦合、高内聚,组件可在运行时动态注入、替换和移除,系统因此具备高度灵活性。
- 从类组合到 Agent 组合 — AI Agent 的能力是动态发现、上下文敏感的,继承的静态结构无法适应这种不确定性,三层组合模型(能力/行为/记忆)是更优解。
- 组合不是银弹 — 过度组合同样有害,Mediator 模式是解决组件间耦合过密问题的推荐方案。
继承与组合:三十年争论的启示
GoF 设计模式的启示
1994年,四位软件工程大师(Gang of Four)在《设计模式》一书中写下了 software engineering 史上最具影响力的建议之一:
“Favor object composition over class inheritance.” (优先使用对象组合,而非类继承。)
这句话不是否定继承的价值,而是指出了继承被滥用的普遍问题。
Java 时代的继承滥用
在 2000 年代的 Java 企业开发中,继承滥用达到了顶峰:
继承带来的问题:
| 问题 | 表现 | 后果 |
|---|---|---|
| 脆弱的基类 | 修改父类影响所有子类 | 不敢改动核心代码 |
| 层次过深 | 继承链超过 3-4 层 | 理解成本高,调试困难 |
| 强耦合 | 子类依赖父类实现细节 | 无法独立演化 |
| 违反封装 | 子类知道父类内部 | 封装被破坏 |
组合的崛起
Go 语言的横空出世,彻底颠覆了 “一切皆类” 的思维模式:
Go 没有继承,只有组合。这不是缺陷,而是设计上的刻意选择。
为什么组合更优
1. 灵活性:运行时 vs 编译时
💡 Key Insight
继承的能力在编译时固定,组合的能力可在运行时替换——这决定了系统的演化方向。
继承是静态绑定: 继承关系在编译时确定,子类无法在运行时改变从父类继承来的行为。
组合是动态绑定: 组合的对象可在运行时注入、替换或移除,系统行为因此具备高度灵活性。
💡 Key Insight
对于需要动态调整能力的 Agent 系统,动态绑定是刚需,组合是唯一可靠的选择。
2. 解耦:接口 vs 实现
💡 Key Insight
“是一个” 带来继承链的传递耦合,”有一个” 则只依赖目标接口——这是解耦的根本差异。
继承的问题是 “是一个”: 子类通过 is-a 关系继承父类,同时也继承了父类的实现细节,父类的变化会直接传导至所有子类。
组合的优势是 “有一个”: 组合只通过 has-a 关系依赖目标接口,实现可替换,调用方无需关心内部细节。
💡 Key Insight
接口契约隔离变化,组合是实现接口隔离原则(ISP)最直接的途径。
3. 测试性:可替换的依赖
💡 Key Insight
单元测试的核心是隔离被测单元——组合通过依赖注入实现真隔离,继承则把父类实现也拉进测试边界。
继承的测试困境: 子类实例化时必须同时初始化父类,测试子类行为往往要连带父类一起覆盖,无法独立进行。
组合的测试便利: 通过依赖注入将 Mock 对象注入被测对象,测试逻辑与被测单元一一对应,Mock/Stub 随心所欲。
💡 Key Insight
可测试性是代码质量的照妖镜——如果测试写起来又贵又慢,设计大概率有问题。
从类组合到 Agent 组合
Agent 系统的特殊性
传统软件组件之间的关系是静态的,但 Agent 系统有独特的挑战:
- 动态能力发现:Agent 可能在运行时学习新技能
- 上下文依赖:同一个 Agent 在不同上下文中表现不同
- 多模态交互:需要组合不同类型的输入/输出能力
- 协作演化:多个 Agent 协作时能力需要动态调整
Agent 组合的三层模型
为什么 Agent 更需要组合
💡 Key Insight
Agent 的能力是动态发现的、上下文敏感的,继承的静态结构无法适应这种不确定性。
场景:一个销售助手 Agent
如果用继承方式设计:一个 SalesAgent 继承 BaseAgent,再继承 LLMExtension、ToolExtension……继承链随功能增加不断膨胀,任何修改都可能动摇基类。
如果用组合方式设计:SalesAgent 持有 LLM、Tools、Memory、Orchestrator 等组件,新能力只需组合新组件,不触碰既有代码。
💡 Key Insight
销售场景中需求变化极快,组合让 Agent 的能力边界跟随业务调整,而无需重构继承树。
实战:设计可组合的 Agent 系统
核心接口设计
1. 能力接口(Capability Interface) — 定义 Agent 必须具备的核心能力,如推理、规划、记忆召回等。每个能力接口约定输入格式、输出格式和调用约束,Agent 实现只需满足接口契约,无需关心调用方细节。
2. 记忆接口(Memory Interface) — 定义 Agent 如何存储、检索和遗忘信息。短期记忆、长期记忆和上下文记忆各自独立,接口统一封装,运行时可按需替换具体实现。
3. 行为接口(Behavior Interface) — 定义 Agent 与外部环境交互的行为模式,包括反应式(event-driven)和主动式(goal-driven)两类。行为接口屏蔽了具体交互协议(API、消息队列、文件等)的差异。
可组合 Agent 的实现
可组合 Agent 以组合而非继承的方式组装上述三大接口的具体实现。Agent 自身仅持有接口引用,具体能力由外部依赖注入(Dependency Injection),从而实现能力与结构的解耦。
具体能力实现示例
推理能力: 基于 LLM 的思维链(Chain-of-Thought)实现,输入任务描述与上下文,输出分步推理结果。推理器本身无状态,可被多个 Agent 共享。
规划能力: 将复杂任务分解为可执行的子任务序列,输入目标状态与约束条件,输出任务图(Task Graph)。规划器依赖能力接口查找可用操作,支持动态重规划。
编排模式:组合的组合
继承在 AI 时代的新形态
Prompt 继承
虽然代码层面避免继承,但 Prompt 设计中出现了新的 “继承” 模式:
Prompt 继承的风险:
- 冲突原则:子 Prompt 可能覆盖或矛盾父 Prompt 的指令
- 长度爆炸:层层继承导致上下文过长
- 调试困难:难以定位是哪个层次的 Prompt 导致了问题行为
更安全的做法:Prompt 组合
Context 继承
Agent 在执行过程中,Context 的传递也呈现出继承特征:
Context 继承的最佳实践:
- 使用
copy()而非直接引用,避免副作用 - 明确哪些字段可继承,哪些需要重置
- 设置深度限制,防止无限递归
反直觉洞察:组合不是银弹
洞察 1:过度组合的危害
**解决方案:
- 最多组合 3-5 个核心组件
- 使用适配器模式统一接口
- 考虑性能开销
洞察 2:继承在特定场景仍然有用
何时使用继承:
- 真正的 “is-a” 关系(几何图形继承体系)
- 领域模型的分类(如上面的 Event 类型)
- 框架/库的扩展点设计
洞察 3:组合也需要设计
更好的设计:Mediator 模式
代码示例与最佳实践
完整示例:销售助手 Agent
最佳实践清单
✅ Do(推荐做法):
- 面向接口编程
- 使用依赖注入
- 保持组件单一职责
-
支持运行时配置 ❌ Don’t(避免做法):
- 避免深继承链
- 避免在子类中依赖父类实现细节
-
避免混合关注点
结尾
组合优于继承不是教条,而是经过三十年软件工程实践验证的设计智慧。
核心要点回顾
| 维度 | 继承 | 组合 |
|---|---|---|
| 关系 | is-a(是一个) | has-a(有一个) |
| 耦合度 | 高(白盒复用) | 低(黑盒复用) |
| 灵活性 | 编译时固定 | 运行时动态 |
| 测试性 | 困难(必须实例化整个继承链) | 容易(注入 Mock) |
| 适用场景 | 真正的分类体系 | 功能能力的组装 |
给 AI 开发者的建议
- 从组合开始:设计 Agent 时,先用组合思维思考能力如何组装
- 延迟使用继承:只有当 “is-a” 关系非常明显时才考虑继承
- 关注接口:组件之间的契约比实现更重要
- 保持简单:不要过度设计,3-5 个核心组件通常是最佳平衡点
最后的话
“软件设计的本质是在约束中寻找平衡。组合给了你灵活性,但也需要更多设计思考。继承看似简单,却可能在未来埋下隐患。”
在 AI Agent 开发中,这一点尤为重要。Agent 系统需要快速迭代、灵活调整,组合设计让这种调整成为可能。
选择组合,不是因为简单,而是因为正确。
深度阅读时间:约 9 分钟
📚 延伸阅读
本系列文章
经典参考
- Design Patterns: Elements of Reusable Object-Oriented Software - GoF 经典
- Composition over Inheritance - Wikipedia
- Effective Java - Joshua Bloch
- The Go Programming Language - Go 语言设计哲学
Agent OS 系列 - 设计模式篇 由 @postcodeeng 整理发布
Published on 2026-03-15 阅读时间:约 20 分钟
下一篇预告:《Agent 的状态机设计》
💬 评论
💡 使用 GitHub 账号登录 即可参与讨论