Ran Wei /面向对象设计/第五模块
EN
面向对象设计 — Ran Wei

第五模块:组合与聚合

由对象构建对象——生命周期所有权与共享引用、UML实心/空心菱形符号与SysML v2的part vs ref。

预备知识: OOD 第四模块Python 3UML 2.5SysML v2
1

组合与聚合

🏠 生活类比
想象一栋房子与它的房间(组合):房子拆了,房间也消失。对比图书馆与它的藏书(聚合):图书馆关闭了,书本身依然存在,可以转移到其他地方。

has-a关系的两种形式,具有截然不同的生命周期语义:

关系含义与生命周期
组合 ◆强所有权。子对象在父对象内部创建,与父对象共存亡。删除父对象会删除子对象。
聚合 ◇弱引用。子对象独立存在,可从多处共享引用。删除容器不影响被引用对象。
ℹ️ 关键判断标准
删除容器,被包含的对象是否也应该被销毁?是 → 组合。否(独立存在,可被他处引用)→ 聚合。
2

组合实践

Order在内部创建OrderItemPayment——它们在Order之外没有任何意义:

Python
class Order:
    def __init__(self, order_id, customer):
        self.order_id=order_id; self._status="draft"
        self._items: list[OrderItem] = []   # 组合:在此创建
        self._payment: Payment | None = None

    def add_item(self, book, qty=1):
        self._items.append(OrderItem(book, qty, book.price))

    def place(self, method):
        self._payment = Payment("PAY-"+self.order_id, self.total(), method)
        self._payment.process(); self._status="placed"

    def cancel(self):
        if self._payment: self._payment.refund()
        self._status="cancelled"  # _items和_payment随后被垃圾回收
diagram
图1 — 实心菱形:Order拥有OrderItem(1..*)和Payment(1)。OrderItem到Book的虚线箭头是聚合——Book在Inventory中独立存在。
3

聚合实践

ShoppingCartInventory持有Book的引用但不拥有所有权。同一个Book对象可以同时被多个容器引用:

Python
class ShoppingCart:
    def __init__(self, cart_id):
        self._items: list[Book] = []   # 聚合:外部引用

    def add_item(self, book): self._items.append(book)
    def remove_item(self, book): self._items.remove(book)  # book依然存在

    def to_order(self, order_id, customer):
        order = Order(order_id, customer)
        for book in self._items: order.add_item(book)
        self._items.clear()   # 购物车清空,书本身仍在其他地方存在
        return order
diagram
图2 — 空心菱形:ShoppingCart和Inventory都引用相同的Book对象,但不拥有它们。
💡 对象同一性
两个容器持有对完全相同Python对象的引用。当Inventory更新Book的库存时,引用该Book的购物车立即能看到变化——它们共享同一个对象。
4

组合 vs 继承

场景正确关系
"DigitalBook是一种Book"继承 ✔ — 明确且永久成立
"ShoppingCart拥有Book"聚合 ✔ — 购物车不是一种Book
"Order拥有Payment"组合 ✔ — 付款是订单的组成部分
"带日志功能的Customer"组合或装饰器 ✔ — 不是新的Customer类型
⚠️ 注意
优先选择组合而非继承。is-a测试不能明确且永久成立时,请使用组合。深层继承层次结构很脆弱——修改父类会影响所有子类。
5

SysML v2 对应

SysML v2
// 组合:Order拥有其子对象
part def Order {
    part items   : OrderItem[1..*];  // 拥有的生命周期
    part payment : Payment;          // 拥有的生命周期
}
// 聚合:ShoppingCart引用共享的Book
part def ShoppingCart {
    ref items : Book[0..*];          // 不拥有——共享引用
}
Python / OOD
SysML v2
组合(内部创建)
part x : ChildClass
聚合(外部引用)
ref x : SomeType
UML实心菱形 ◆
part关键字(拥有的生命周期)
UML空心菱形 ◇
ref关键字(共享引用)
6

总结

组合 ◆

强所有权。子对象随所有者创建和销毁。Order拥有OrderItem和Payment。

聚合 ◇

共享引用。对象独立存在,可从多处共享。

关键判断

删除容器=销毁子对象?是=组合。否=聚合。

vs继承

is-a不明确时优先选择组合。产生更少耦合,更易于更改。

Python

组合=内部构建。聚合=接受参数或引用现有对象。

SysML v2

part=拥有生命周期。ref=共享引用。

📚
下一讲 — 第六模块:接口与契约

抽象基类作为接口、Python Protocol结构子类型、契约式设计,以及SysML v2的interface def映射。