聚合与组合的核心区别在于部分对象的生命周期由谁管理:聚合中部分可独立存在、被多方复用,整体仅持引用;组合中部分由整体创建并销毁,不可脱离整体存在。

聚合和组合的核心区别,就落在“谁管对象的命”上——不是看类里有没有引用,而是看这个对象由谁创建、归谁负责、能不能复用。
生命周期谁说了算?
聚合中,部分对象自己活自己的:它可能在系统其他地方被创建,也能被多个整体同时引用。整体销毁时,只是断开引用,对象本身还在内存里,甚至能继续服务别的模块。
组合则相反:部分对象一出生就绑定整体,整体一走,它基本也就跟着退出。比如订单取消后,订单项自动失效;窗口关闭,里面所有按钮实例也该释放。
- 聚合:Employee 实例可被 Department、Project、Team 多次引用,Department.clear() 不影响 Employee 存活
- 组合:OrderItem 实例只属于唯一 Order,Order 被 GC 后,若无其他强引用,OrderItem 自然回收
代码里一眼识别的关键信号
不靠 UML 图,也不靠命名,直接看构造逻辑:
— 如果整体类内部 new 了部分对象,或者在构造函数里悄悄初始化了成员(比如 this.engine = new Engine()),就是组合倾向;
— 如果整体只接收外部传入的引用(比如 public Department(List
- 聚合写法:构造器或 setter 接收已有对象,add/remove 只操作引用
- 组合写法:内部 new 或通过工厂获取,整体类承担资源分配与释放责任(如显式 close、dispose)
- 误判高发区:List
成员变量 ≠ 聚合;关键看 List 里的元素是谁造的、谁管的
共享性决定架构弹性
聚合天然支持解耦和复用:一个 Student 对象可同时出现在多个 ClassRoom、多个 CourseRegistration 中,方便做微服务拆分或独立部署 Student 服务。
组合则强化内聚:Engine 不该脱离 Car 存在,Button 不该脱离 Window 独立渲染——这种强归属让模块边界更清晰,但也限制了跨上下文复用。
- 适合聚合的场景:用户与角色、订单与优惠券(同一张券可被多订单临时关联)、教室与设备(投影仪可调拨到不同教室)
- 适合组合的场景:表单与输入控件、树节点与子节点、HTTP 请求与 Header/Body 实例
实战中容易踩的坑
最常见的是“伪聚合”:表面接收外部引用,但内部偷偷缓存、修改或延长生命周期。比如把传入的 Employee 列表深拷贝一份再存,实际已变成组合语义;又或者在整体销毁时主动调用部分对象的 shutdown 方法,越权干预其生命周期。
- 测试困难:组合对象难 mock,聚合对象可轻松替换为 stub
- 内存泄漏:组合中本该释放的对象被聚合式引用意外持有时,会滞留
- 部署风险:把本该聚合的 Service 实例硬编码进主模块,导致无法独立升级

















