为什么大厂架构师总在画图?为什么设计文档里那些方框、箭头、三角让你一头雾水?今天,我们彻底搞懂 UML 类图,让你不仅能画,更能看懂背后的设计思想。
在软件开发中,沟通成本往往远超编码成本。当你需要向团队描述一个设计模式、解释系统模块关系、或者重构遗留代码时,UML 类图就是最通用的"语言"。它像建筑行业的施工图,把代码中的类、接口、关系可视化,让设计一目了然。本文将从基本符号到复杂关系,带你掌握 UML 类图的核心知识。
一、什么是 UML 类图?
UML(Unified Modeling Language,统一建模语言) 是一种标准化的软件系统建模语言。而类图(Class Diagram) 是 UML 中最重要、最常用的图之一,用于描述系统的静态结构------即系统中存在哪些类、接口,以及它们之间的相互关系。
一句话总结:类图 = 类 + 接口 + 关系(依赖、关联、聚合、组合、继承、实现)。
为什么重要?
- 沟通工具:让非技术人员(产品、测试)也能理解设计。
- 设计文档:为后续开发、维护提供依据。
- 代码生成/逆向:某些 IDE 支持依据类图生成骨架代码,或从代码反向生成类图。
二、类图的基本要素
2.1 类的表示
一个类用矩形表示,通常分为三层:
text
┌─────────────────┐
│ 类名 │ <- 第一层:类名
├─────────────────┤
│ - field1: Type │ <- 第二层:属性
│ + field2: Type │
├─────────────────┤
│ + method1() │ <- 第三层:方法
│ - method2() │
└─────────────────┘
- 可见性 :
+公有(public),-私有(private),#受保护(protected),~包级私有(package/default)。 - 属性格式 :
可见性 名称: 类型 [= 默认值] - 方法格式 :
可见性 名称(参数列表): 返回类型
示例 :- balance: double = 0.0 表示私有 double 类型字段,初始值 0.0。
2.2 接口的表示
接口有两种常用表示方式:
-
矩形 + <<interface>> stereotype:
text┌─────────────────┐ │ <<interface>> │ │ Drawable │ ├─────────────────┤ │ + draw() │ └─────────────────┘ -
圆圈表示法(棒棒糖表示法):在实现类旁边画一个圆圈,圆下面写接口名,从圆圈连线到实现类(较少用,但简洁)。
2.3 抽象类
类名用斜体 表示,或者添加 {abstract} 标记。抽象方法也使用斜体。
text
┌─────────────────┐
│ AbstractClass │ <- 斜体
├─────────────────┤
│ + operation() │ <- 斜体
└─────────────────┘
三、类之间的关系(重点!)
UML 类图的精髓在于关系。搞懂以下六种关系及其符号,就能读懂绝大多数类图。
3.1 依赖(Dependency)------ 临时用一下
定义 :一个类使用了另一个类,通常作为局部变量、方法参数或静态方法调用。依赖是最弱的耦合,表示"知道对方的存在"。
符号 :虚线箭头 -----> ,箭头指向被依赖者。
例子 :OrderService 依赖 Logger 来记录日志。
text
OrderService --------> Logger
代码:
java
public class OrderService {
public void createOrder() {
Logger.log("Order created"); // 方法内使用
}
}
3.2 关联(Association)------ 长期持有
定义 :一个类持有另一个类的引用作为字段,表示一种"拥有"或"使用"关系。关联可以是单向或双向的。
符号 :实线箭头 ---->(单向),无箭头实线(双向,不推荐)。可以标注多重性和角色名。
角色与多重性:
1:恰好一个0..1:零或一个*或0..*:零到多个1..*:一个到多个
例子 :Employee 和 Department,一个部门有多个员工,一个员工属于一个部门。
text
Department "1" ------> "*" Employee
代码:
java
public class Department {
private List<Employee> employees; // 持有多个Employee
}
public class Employee {
private Department dept; // 持有Department
}
3.3 聚合(Aggregation)------ 弱"整体-部分"
定义:表示"整体-部分"关系,但部分可以脱离整体独立存在。整体和部分生命周期不同。
符号:空心菱形 + 实线箭头,菱形在整体端。
例子 :Car 和 Wheel。一辆车有四个轮子,轮子拆下来还能装到别的车上。
text
Car <>------> Wheel
代码:
java
public class Car {
private List<Wheel> wheels; // 轮子由外部传入
public Car(List<Wheel> ws) { wheels = ws; }
}
3.4 组合(Composition)------ 强"整体-部分"
定义 :更强的聚合,部分不能脱离整体独立存在。部分的生命周期由整体管理(整体销毁时,部分也随之销毁)。
符号:实心菱形 + 实线箭头。
例子 :House 和 Room。房间不能脱离房子存在。
text
House ●------> Room
代码:
java
public class House {
private List<Room> rooms;
public House() {
rooms = new ArrayList<>(); // 房间在House内部创建
rooms.add(new Room());
}
// 没有暴露rooms的setter,外部无法替换rooms
}
3.5 泛化(Generalization)------ 继承
定义:类与类之间的继承关系(或接口与接口的继承)。子类继承父类所有属性和方法。
符号:空心三角 + 实线,三角指向父类。
例子 :Animal 是父类,Dog 继承它。
text
Animal
△
|
Dog
代码:
java
public class Dog extends Animal { }
3.6 实现(Realization)------ 接口实现
定义:类实现一个接口。
符号 :空心三角 + 虚线,三角指向接口。
text
<<interface>>
Drawable
△
|
(虚线)
|
Circle
代码:
java
public class Circle implements Drawable { }
四、关系强度对比
| 关系 | 符号 | 箭头 | 耦合度 | 生命周期关联 | 典型代码特征 |
|---|---|---|---|---|---|
| 依赖 | 虚线箭头 | 指向被依赖 | 弱 | 无 | 局部变量、方法参数 |
| 关联 | 实线箭头 | 指向被关联 | 中 | 无 | 成员变量 |
| 聚合 | 空心菱形 + 实线 | 菱形指向整体 | 较强 | 部分可独立 | 成员变量,整体不负责部分创建/销毁 |
| 组合 | 实心菱形 + 实线 | 菱形指向整体 | 强 | 部分随整体同生共死 | 成员变量,整体负责创建/销毁 |
| 泛化 | 空心三角 + 实线 | 三角指向父类 | 强 | --- | extends |
| 实现 | 空心三角 + 虚线 | 三角指向接口 | 强 | --- | implements |
五、综合示例:电商系统局部类图
text
┌──────────────┐ ┌──────────────┐
│ OrderService│------>│ Logger │ 依赖
└──────────────┘ └──────────────┘
│
│ 关联(1 → *)
v
┌──────────────┐ ┌──────────────┐
│ Order │◇─────>│ OrderItem │ 聚合(?? 实际应为组合)
└──────────────┘ └──────────────┘
△
│ 继承
│
┌──────────────┐
│ SpecialOrder│
└──────────────┘
说明:
OrderService依赖Logger(虚线箭头)。OrderService关联Order(实线箭头,多重性 1 → *)。Order和OrderItem实际应为组合(实心菱形),因为订单项离开订单无意义。但上图中画成了聚合(空心菱形),注意甄别。
六、绘制类图的工具推荐
- PlantUML:文本描述转图形,适合嵌入 Markdown。
- Draw.io / diagrams.net:免费,支持多种导出,与网盘集成。
- Lucidchart:功能强大,在线协作。
- StarUML:专业 UML 工具,支持多种建模。
- VS Code 插件:如 PlantUML、Draw.io Integration。
七、常见误区澄清
误区1:聚合和组合区分不清。
- 经验法则:如果两个类生命周期独立,用聚合;如果部分对象由整体创建且整体销毁时部分也销毁,用组合。
误区2:把所有成员变量关系都画成关联。
- 实际上,依赖于局部变量或方法参数的关系应该用依赖,而不是关联。
误区3:把所有关系都画成双向关联。
- 双向关联增加耦合,应该尽量避免。大多数关系是单向的。
误区4:类图要包含所有字段和方法。
- 类图是设计视图,只需画出关键属性和方法,帮助理解职责即可,不必面面俱到。
八、总结
UML 类图是软件开发者的通用语言。掌握它,你就能与团队高效沟通设计,也能从开源项目中快速理解架构。记住:
- 方框 = 类/接口
- 实线/虚线 = 关系种类
- 箭头方向 = 依赖方向(指向被依赖者)
- 菱形 = 整体-部分
- 三角 = 继承/实现
从今天开始,尝试把你项目中的核心模块用类图画出来,你会发现设计缺陷、循环依赖、耦合过高这些"隐藏代码臭味"立刻现形。
思考题:在经典的 MVC 模式中,View 和 Controller 之间是什么关系?依赖、关联还是其他?欢迎评论区讨论。
如果觉得有帮助,点赞、收藏、转发~
本文首发于 CSDN,未经授权禁止转载。