⌨️ 第五章 从设计到测试
📄 本篇摘要

类与类的关系按【黏度】从弱到强排一排:依赖(用过就走)→ 关联(长期认识)→ 聚合(整体留着部分,部分也能独立活)→ 组合(整体亡则部分亡)→ 泛化(继承,is-a)。判据只有一个:部分对象的生命周期归谁管。

📝 关系

上一节:UML 与设计模式 | 下一节:命名空间、模块与包

一、五级关系光谱

关系 UML 记号 一句话 例子
依赖 虚线箭头 方法参数里用一下 函数里调了 hash()
关联 实线箭头 长期持有一个引用 订单记着客户
聚合 空心菱形 has-a,但部分可独立活 车队和学生(学生退队不死)
组合 实心菱形 has-a,部分随整体同生共死 订单和订单行(删订单行必删)
泛化 空心三角 is-a,继承(5-16) 狗是动物

判分标准=生命周期归属:部分的生灭由整体掌控(建订单时建行、删订单时删行)→ 组合;部分独立存在(学生不因退队而消失)→ 聚合。分不清时问一句:删了整体,部分要不要一起删?

二、为什么这个区别重要

关系强度决定改动波及面:依赖最弱(改哈希函数不用通知你);组合最强(订单行格式一变,订单跟着动)。设计的目标:能用弱关系就不用强关系——组合过深=牵一发动全身,这正是 5-11 重构时【霰弹式修改】坏味道的根源。

三、动手 Demo:聚合与组合一测便知

class Student:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name

class Team:                       # 聚合:学生先存在,入队而已
    def __init__(self, title):
        self.title = title
        self.members = []
    def join(self, s):
        self.members.append(s)

class Order:                      # 组合:订单行随订单生灭
    def __init__(self, no, items):
        self.no = no
        self.lines = [{"item": n, "qty": q} for n, q in items]
    def total(self):
        return sum(l["qty"] for l in self.lines)

# 聚合侧
b = Student("波仔"); x = Student("小红")
team = Team("编程小组")
team.join(b); team.join(x)
del team                          # 团队没了……
print("学生还活着:", b, x)

# 组合侧
order = Order("SO-1", [("键盘", 2), ("鼠标", 3)])
print("订单行总数:", order.total())
del order                         # 订单没了……
# order 的订单行无处可达,随之消亡 —— 生命周期被整体掌控
print("订单行随订单消亡(无可达引用)")

沙箱实跑输出:

学生还活着: 波仔 小红
订单行总数: 5
订单行随订单消亡(无可达引用)

看到什么算成功:删掉 Team 后两个学生引用依然可用;Order 消亡后其订单行无法再访问。

读结果:Team 对 Student 是聚合——只存引用,成员在队外本来就存在;Order 对订单行是组合——行在构造订单时才创建、随订单一起被回收。代码上的判据:构造函数里创建的=组合;外面传入的=聚合。

四、30 秒实验

把 Team.join 的成员改存 s.name(字符串副本)——引用断了,改名字团队不知道。聚合传引用不传拷贝:拷贝出的第二条记录就是 5-11 说的重复数据隐患。

五、常见误区

误区 真相
聚合和组合随便选 生命周期归属是硬判据;选错则删除行为(级联删)全乱
继承和组合是竞争关系 不同维度:泛化表 is-a、聚合组合表 has-a;先分清哪种关系再选工具
关系画了图就算设计完 关系强度决定改动波及面——弱关系优先是设计目标
自测三问(点击展开) 1. **聚合与组合的判据?** 答:部分对象的生命周期是否被整体掌控——掌控是组合(实心菱形),独立是聚合(空心菱形)。 2. **五级关系按黏度从弱到强?** 答:依赖 → 关联 → 聚合 → 组合 →(泛化另维)。 3. **代码上怎么快速分辨组合与聚合?** 答:整体构造函数里创建部分=组合;部分从外部传入=聚合。

相关笔记