UML 类图:从符号语义到设计意图的阅读框架
六种线条,画出的不是符号,而是设计者对耦合强度的判断——读懂了这一点,类图就从备忘录变成了设计对话。
UML 类图是面向对象建模中最常用的图。常见的九种 UML 图中,类图专门描述系统中类与类之间的静态关系。大多数教程把类图当作符号表来教:虚线空心箭头是实现,实线空心箭头是泛化,菱形实线是组合……记住了符号,却不知道为什么。
问题是,符号本身不是重点。每种线条背后都是设计者对 耦合强度 的判断——这两个类应该绑多紧?生命周期是否耦合?变化会不会传导?掌握这个判断框架,比记住六种箭头画法重要得多。
下面用一张完整的类图作为贯穿全文的例子:

图中 车 是抽象类(<<abstract>>),下方展开了六种关系。我们从最弱的关系说起。
碰面:依赖关系(dependency)
依赖是六种关系中耦合最弱的。它表示"我在某个时刻会用到你,但不需要一直持有你"。

依赖用带箭头的虚线表示,A 指向 B 意味着 A 依赖 B。在代码中,依赖通常体现为方法参数、局部变量或静态方法调用——A 不持有 B 的引用,只是运行到某个方法时临时碰个面。
学生骑自行车上学,学生只在 goToSchool() 方法里临时创建或引用自行车,方法结束关系就结束了。这是依赖关系的核心特征:临时性、局部性。
双向依赖是非常糟糕的结构——A 依赖 B,B 也依赖 A,两个类互相纠缠,改一个必须改另一个。总是保持单向依赖,是降低系统复杂度的基本纪律。
相识:关联关系(association)
关联比依赖更强,表示"我持续地知道你"。两个对象之间存在结构性的、长期的关系,在程序中通常体现为成员变量。

关联用实线表示。默认不强调方向时,表示双方互相知道。加箭头则表示单向:A 知道 B,但 B 不知道 A。学生持有身份证,就是一个关联关系——学生对象有身份证成员变量,这种关系贯穿对象的整个生命周期,不是临时用一下就丢掉的。
关联关系描述的是静态的、天然的结构关系,由业务域决定。乘车人与车票、学生与学校——这些关系不是运行时才产生的,而是领域模型的一部分。
松绑:聚合关系(aggregation)
聚合是"整体与部分"关系中较松的一种:整体由部分组成,但部分可以脱离整体独立存在。

聚合用带空心菱形的实线表示,菱形端是整体。学生与班级是聚合关系——班级由学生组成,但班级撤销后学生依然存在。
聚合关系的关键判断标准是 生命周期独立性:部分在整体之外是否有意义。如果答案是"是",用聚合。在代码层面,聚合与关联的差别并不大——同样是成员变量。区别在于 设计意图:聚合明确表达了"整体-部分"的语义,告诉读图的人"这些对象组成一个集合,但彼此不绑定生死"。
绑定:组合关系(composition)
组合是"整体与部分"关系中更强的一种:整体拥有部分,部分不能脱离整体独立存在。

组合用带实心菱形的实线表示。小汽车与发动机是组合关系——发动机是小汽车的一部分,小汽车报废了发动机也就没了。
组合关系的关键特征是 生命周期耦合:整体消亡时部分一同消亡。公司撤销了部门就不存在,但部门撤销了人员还在——所以公司与部门是组合,部门与人员是聚合。
组合在代码中通常体现为在构造函数中创建部分对象,在析构时释放。部分不需要、也不应该被外部创建后传入——因为它的生命周期由整体全权管理。
传承:泛化与实现
泛化和实现都表达 "is-a" 关系——子类是父类的一种。区别在于父类的抽象程度。
泛化(generalization)用带空心箭头的实线表示,子类指向父类。泛化关系中的父类是具体类,可以直接实例化。SUV 是 小汽车 的具体子类——这层关系是泛化。

实现(realization)用带空心箭头的虚线表示。实现关系中的父类是抽象类或接口,不能直接实例化。自行车实现了 车 这个抽象类——"车"本身不能定义对象,只有指明具体子类才有意义。

两者的代码结构几乎相同(都是继承),但设计意图不同。泛化说"我是你的一种特化",实现说"我履行你定义的契约"。在 C++ 中,抽象类用纯虚函数表示;在 Java 中,interface 让这个区分更清晰。
从符号到设计意图
六种关系排成一列,实际上构成了一个 耦合强度的光谱:
| 关系 | 耦合强度 | 生命周期 | 代码体现 |
|---|---|---|---|
| 依赖 | 最弱 | 临时 | 方法参数、局部变量 |
| 关联 | 弱 | 独立 | 成员变量 |
| 聚合 | 中 | 部分独立 | 成员变量(整体-部分语义) |
| 组合 | 强 | 共生共灭 | 构造时创建,析构时释放 |
| 泛化/实现 | 最强 | 编译期绑定 | 继承 |
从依赖到组合,耦合一步步收紧,灵活性一步步降低。设计者在类图上选择哪条线,就是在回答一个根本问题:这两个类应该绑多紧?
选依赖还是关联,问的是"我是临时用你还是持续持有你"。选聚合还是组合,问的是"部分能不能脱离整体存活"。选泛化还是实现,问的是"父类是具体概念还是抽象契约"。
这些选择没有标准答案,但有一个统一的原则:在满足需求的前提下,选择耦合最弱的关系。能用依赖就不要用关联,能用聚合就不要用组合。每多用一分耦合,系统就多一分僵化——这个代价在需求变化时会成倍放大。
下次拿到一张类图,不要先看箭头形状,先看线条两端的对象关系:它们是临时碰面还是长期共处?是平等协作还是整体拥有部分?是具体特化还是抽象契约?箭头会告诉你答案,但问题应该由你来问。