聚合(Aggregation)和组合(Composition)是面向对象编程(OOP)、UML 建模中描述整体 - 部分 关系的两种核心关联类型,二者都表示 "整体包含部分",但核心区别在于部分的生命周期是否依赖整体------ 这是理解二者的关键。
一、先明确核心差异(一句话总结)
| 维度 | 聚合(Aggregation) | 组合(Composition) |
|---|---|---|
| 核心关系 | "拥有" 关系(has-a):整体拥有部分,但部分可独立存在 | "包含" 关系(contains-a):整体由部分构成,部分无法脱离整体存在 |
| 生命周期依赖 | 部分的生命周期独立于整体(整体销毁,部分仍可存活) | 部分的生命周期完全依赖整体(整体销毁,部分必销毁) |
| UML 图示 | 空心菱形 + 实线(菱形在整体端) | 实心菱形 + 实线(菱形在整体端) |
| 代码体现 | 部分对象通常由外部传入,整体不负责创建 / 销毁 | 部分对象由整体创建(如在构造函数中 new),整体销毁时主动销毁部分 |
| 示例 | 公司 - 员工、汽车 - 轮胎(可拆下来装另一辆车) | 人体 - 心脏、订单 - 订单明细(明细不能脱离订单存在) |
二、详细拆解
1. 聚合(Aggregation):松散的整体 - 部分关系
聚合是弱关联的整体 - 部分关系,重点是 "整体拥有部分,但部分可独立存在"。
-
核心特征:
- 部分可以属于多个整体(或不属于任何整体);
- 整体的创建 / 销毁不会影响部分的生命周期;
- 代码中通常通过 "依赖注入"(如构造函数传参、setter 方法)将部分对象传入整体,整体仅持有引用,不负责部分的创建和销毁。
-
示例 1:公司(Company)和员工(Employee)
-
公司是 "整体",员工是 "部分";
-
员工可以加入 A 公司,也可以离职加入 B 公司,甚至暂时无公司(独立存在);
-
即使 A 公司解散(整体销毁),员工依然存在(部分存活)。
-
代码示例(Java): java
运行
// 部分类:员工(可独立存在) class Employee { private String name; public Employee(String name) { this.name = name; } } // 整体类:公司(拥有员工,但不创建/销毁员工) class Company { private List<Employee> employees; // 员工由外部传入,公司仅持有引用 public void addEmployee(Employee emp) { employees.add(emp); } // 公司解散时,不会主动销毁员工 public void dissolve() { employees.clear(); // 仅解除引用,员工对象仍存在 } } // 使用:员工可独立创建,再加入公司 public class Test { public static void main(String[] args) { Employee emp1 = new Employee("张三"); // 员工独立存在 Company company = new Company(); company.addEmployee(emp1); // 公司"拥有"员工 company.dissolve(); // 公司解散,emp1仍存在 } }
-
-
示例 2:汽车(Car)和轮胎(Tire)
- 轮胎可以先生产出来存放,再装配到某辆汽车上;
- 汽车报废后,轮胎可拆下来装到另一辆车上,轮胎本身不会随汽车销毁。
2. 组合(Composition):强耦合的整体 - 部分关系
组合是强关联的整体 - 部分关系,重点是 "部分是整体的不可分割的组成部分,无法独立存在"。
-
核心特征:
- 部分只能属于一个整体,不能共享;
- 整体创建时,部分必须同时创建(通常在整体的构造函数中初始化);
- 整体销毁时,部分必须被销毁(如 Java 中整体对象被 GC 回收,部分对象也会被回收;或手动释放资源);
- 部分不能脱离整体被单独使用。
-
示例 1:订单(Order)和订单明细(OrderItem)
-
订单是 "整体",订单明细是 "部分";
-
订单明细无法脱离订单存在(没有订单的 "明细" 无业务意义);
-
创建订单时,明细必须随订单一起创建;删除订单时,明细必须一起删除。
-
代码示例(Java): java
运行
// 部分类:订单明细(无法独立存在) class OrderItem { private Long productId; private Integer quantity; public OrderItem(Long productId, Integer quantity) { this.productId = productId; this.quantity = quantity; } } // 整体类:订单(创建/销毁明细) class Order { private Long orderId; private List<OrderItem> items; // 订单创建时,初始化明细(部分由整体创建) public Order(Long orderId) { this.orderId = orderId; this.items = new ArrayList<>(); } // 新增明细:由订单内部创建,不接受外部传入的明细(强控制) public void addItem(Long productId, Integer quantity) { items.add(new OrderItem(productId, quantity)); } // 订单销毁时,明细也被销毁(Java中GC自动回收,若为资源需手动释放) public void cancel() { items.clear(); // 明细随订单销毁 } } // 使用:明细无法独立创建和使用 public class Test { public static void main(String[] args) { Order order = new Order(1L); order.addItem(1001L, 2); // 明细由订单创建 order.cancel(); // 订单取消,明细也被销毁 // 无法直接创建OrderItem并使用(无业务意义) } }
-
-
示例 2:人体(Person)和心脏(Heart)
- 心脏是人体的组成部分,不能脱离人体独立存在;
- 人体死亡(整体销毁),心脏也会停止工作(部分销毁);
- 心脏无法属于多个人体。
三、易混淆场景的判断方法
遇到整体 - 部分关系时,用以下两个问题快速区分:
- 部分能否脱离整体独立存在?
- 能 → 聚合(如:书 - 作者,作者可写多本书,也可不写书);
- 不能 → 组合(如:手机 - 电池,手机专用电池无法脱离手机单独使用)。
- 整体销毁时,部分是否必须销毁?
- 否 → 聚合(如:学校 - 学生,学校解散,学生仍存在);
- 是 → 组合(如:购物车 - 购物车项,购物车删除,项必删除)。
四、和数据库主从表的关联(补充)
你之前问的主从表,和聚合 / 组合的对应关系:
- 组合关系 → 主从表强关联(从表外键不允许 NULL,删除主表级联删除从表);
- 聚合关系 → 主从表弱关联(从表外键允许 NULL,删除主表不影响从表)。
比如:
- 订单(主表)- 订单明细(从表)是组合关系 → 明细外键不允许 NULL,删订单必删明细;
- 部门(主表)- 员工(从表)是聚合关系 → 员工外键(部门 ID)允许 NULL(员工可待岗),删部门不删员工(仅置空部门 ID)。
总结
聚合和组合都是 "整体 - 部分" 关系,核心区别是生命周期依赖强度:
- 聚合是 "松散拥有",部分独立;
- 组合是 "紧密构成",部分依赖整体存活。在设计时,组合用于 "不可分割" 的场景,聚合用于 "可独立存在" 的场景,这也是 UML 建模和代码设计中保证逻辑合理性的关键。