1. 什么是类图?
想象一下,你要向朋友解释一个复杂的软件系统如何工作。你会从哪里开始?可能会先描述系统由哪些部分组成,每个部分负责什么功能,以及它们如何相互协作。这就是类图要做的事情。
1.1 类图的定义
类图(Class Diagram)是统一建模语言(UML)中最常用的一种结构图,它展示了系统的静态结构。简单来说,类图回答了三个关键问题:
- 系统中有哪些类? - 识别出所有的类
- 每个类包含什么? - 类的属性和方法
- 类之间有什么关系? - 类如何相互连接和交互
1.2 为什么需要类图?
在我刚开始编程时,经常陷入"代码先行,设计后补"的误区。直到一个项目因为类关系混乱而不得不重构时,我才真正理解了类图的价值:
现实案例: 我们团队曾开发一个电商系统。起初,每个人都按照自己的理解添加类和方法。几周后,当需要添加促销功能时,发现Order、Cart、Product类之间关系错综复杂,修改一个地方会引发多处错误。这时,我们画了一个类图,立即发现了问题:
- 循环依赖
- 职责不清的类
- 缺失的关键关系
java
// 重构前的混乱代码示例
class Order {
public void addProduct(Product p) {
// 直接操作Cart中的产品列表
Cart.getInstance().getProducts().add(p);
}
}
class Cart {
public void createOrder() {
// 复制了大量Order的逻辑
}
}
// 重构后的清晰结构
class Cart {
private List<CartItem> items;
public Order checkout() {
return new Order(this.items);
}
}
class Order {
private List<OrderItem> items;
public Order(List<CartItem> cartItems) {
// 转换逻辑
}
}
1.3 类图的用途
- 设计阶段:可视化软件架构,发现设计缺陷
- 开发阶段:为程序员提供清晰的蓝图
- 文档阶段:作为系统设计的永久记录
- 沟通阶段:让非技术人员也能理解系统结构
2. 类图的组成
类图由三个基本元素构成:类、属性和方法。让我们通过一个具体的例子来理解。
2.1 类的表示
在类图中,一个类用一个三层的矩形表示:
┌───────────────────┐ ← 类名层
│ Student │
├───────────────────┤ ← 属性层
│ - name: String │
│ - age: int │
│ - id: String │
├───────────────────┤ ← 方法层
│ + getName(): String│
│ + setAge(age: int)│
│ + study(): void │
└───────────────────┘
访问修饰符符号:
+公共(public) - 对所有类可见-私有(private) - 仅对本类可见#保护(protected) - 对子类可见~包内可见(package/default) - 对同包类可见
2.2 属性的详细语法
属性不仅仅是名称和类型,还可以包含更多信息:
可见性 名称: 类型 [多重性] = 默认值 {约束}
示例:
java
// 对应的Java代码
public class University {
private String name;
private List<Student> students = new ArrayList<>();
private static final int MAX_CAPACITY = 10000;
// 方法...
}
类图中的表示:
- name: String
- students: List<Student> [0..*] = new ArrayList<>()
- MAX_CAPACITY: int {readOnly} = 10000
2.3 方法的完整表示
方法同样可以有详细的描述:
可见性 方法名(参数列表): 返回类型 {约束}
示例对比:
java
// Java代码
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public static double sqrt(double x) {
return Math.sqrt(x);
}
}
类图中的表示:
+ add(a: int, b: int): int
+ sqrt(x: double): double {static}
2.4 特殊类型的类
1 抽象类
┌───────────────────┐
│ <<abstract>> │
│ Animal │
├───────────────────┤
│ # name: String │
├───────────────────┤
│ + eat(): void │
│ + makeSound(): void {abstract}│
└───────────────────┘
抽象类名通常用斜体表示,或者加上<<abstract>>标记
2 接口
┌───────────────────┐
│ <<interface>> │
│ Drawable │
├───────────────────┤
│ + draw(): void │
│ + resize(): void │
└───────────────────┘
接口通常用<<interface>>标记,方法都是抽象且公开的
3. 类之间的关系:依赖、关联、聚合、组合
这是类图最核心也最容易混淆的部分。理解这些关系的关键是思考类之间连接的强度和生命周期。
3.1 依赖关系(Dependency)
一句话概括: "偶然的、临时的、弱的关系"
特点:
- 最弱的关系类型
- 临时性关联
- 不会影响生命周期
场景:
- 方法参数
- 局部变量
- 静态方法调用
- 返回值类型
表示: 虚线箭头 →
┌──────────┐ ┌──────────┐
│ Teacher │─────>│ Pen │
└──────────┘ └──────────┘
依赖(使用钢笔)
代码示例:
java
class Teacher {
// 依赖关系:Pen作为方法参数
public void writeWith(Pen pen) {
pen.write("Hello");
}
// 依赖关系:Pen作为局部变量
public void signDocument() {
Pen redPen = new Pen("red");
redPen.write("Signature");
}
}
class Pen {
public void write(String text) {
System.out.println(text);
}
}
现实类比: 就像你去咖啡店点咖啡,你依赖咖啡师制作咖啡,但你们之间没有长期关系,咖啡师不是你的"一部分"。
3.2 关联关系(Association)
一句话概括: "长期的、结构性的关系"
特点:
- 比依赖更强的关系
- 长期存在
- 通常表示为类的属性
表示: 实线箭头 → 或双向箭头
┌──────────┐ ┌──────────┐
│ 老师 │─────>│ Student │
└──────────┘ └──────────┘
教导(一个老师教多个学生)
代码示例:
java
class Teacher {
// 关联关系:Teacher长期关联多个Student
private List<Student> students;
public Teacher() {
this.students = new ArrayList<>();
}
public void addStudent(Student student) {
this.students.add(student);
}
}
class Student {
private String name;
}
多重性表示:
1- 一个*或0..*- 零个或多个1..*- 一个或多个0..1- 零个或一个1..3- 一到三个
示例:
┌──────────┐ 1 ┌──────────┐
│ Teacher │◄------------►│ Student │
└──────────┘ * └──────────┘
一个老师对应多个学生
3.3 聚合关系(Aggregation)
一句话概括: "整体与部分,但部分可以独立存在"
特点:
- 一种特殊的关联关系
- 表示"has-a"关系
- 部分可以独立于整体存在
- 空心菱形指向整体
表示:
┌─────────────┐ ┌──────────┐
│ Department │◇─────>│ Teacher │
└─────────────┘ * └──────────┘
部门包含老师,但老师可以独立存在
代码示例:
java
class Department {
// 聚合关系:部门包含老师,但老师可以独立存在
private List<Teacher> teachers;
public Department(List<Teacher> teachers) {
this.teachers = teachers; // 接收已经存在的老师
}
// 即使部门解散,老师仍然存在
public void disband() {
this.teachers = null;
// 老师们还可以去其他部门
}
}
class Teacher {
private String name;
}
3.4 组合关系(Composition)
一句话概括: "强聚合,部分与整体共存亡"
特点:
- 比聚合更强的关系
- 表示"contains-a"关系
- 部分不能独立于整体存在
- 实心菱形指向整体
表示:
┌──────────┐ 1 ┌──────────┐
│ House │◆─────>│ Room │
└──────────┘ * └──────────┘
房子包含房间,房间不能独立于房子存在
代码示例:
java
class House {
// 组合关系:房子包含房间,房间不能独立存在
private List<Room> rooms;
public House() {
// 创建房子时同时创建房间
this.rooms = new ArrayList<>();
this.rooms.add(new Room("客厅"));
this.rooms.add(new Room("卧室"));
}
// 房子被拆除,房间也不复存在
public void demolish() {
for (Room room : rooms) {
// 清理房间资源
}
this.rooms = null;
}
}
class Room {
private String type;
public Room(String type) {
this.type = type;
}
}
3.5 四种关系的对比总结
| 关系类型 | 强度 | 生命周期 | 代码表现 | 箭头 | 现实类比 |
|---|---|---|---|---|---|
| 依赖 | 最弱 | 临时 | 局部变量、参数 | 虚线箭头 | 人与出租车 |
| 关联 | 中等 | 长期 | 成员变量 | 实线箭头 | 老师与学生 |
| 聚合 | 较强 | 独立 | 成员变量(外部传入) | 空心菱形 | 车队与汽车 |
| 组合 | 最强 | 共存 | 成员变量(内部创建) | 实心菱形 | 树与树叶 |
4 正确判断类图关系
遇到不确定的关系时,依次问这三个问题:
- 问题1:生命周期绑定吗?
java
java
// 问:A消失时,B还存在吗?
if (A.destroy() && B.stillExists()) {
return "关联或聚合";
} else {
return "组合";
}
- 问题2:谁创建谁?
java
java
// 问:B是由A创建的吗?
if (A.creates(B)) {
return "组合";
} else if (B.isExternalTo(A)) {
return "关联或聚合";
}
- 问题3:有整体-部分概念吗?
java
java
// 问:B是A的一部分吗?
if (B.isPartOf(A)) {
// 整体-部分关系
if (B.canLiveWithout(A)) {
return "聚合";
} else {
return "组合";
}
} else {
return "关联";
}
常见错误:
- 过度使用组合:不是所有"包含"关系都是组合
- 忽略多重性:忘记标注关系的数量
- 箭头方向错误:依赖和关联的箭头指向被使用/被关联的类
记忆技巧:
- 依赖:虚线 → 最弱 → "用过即弃"
- 关联:实线 → 长期 → "你是我的"
- 聚合:空心菱形 → "你属于我,但可以离开"
- 组合:实心菱形 → "你是我的一部分,同生共死"
5 案例
5.1 需求:
- 提供一个登录和注册用户的界面。
- 提供一个人事信息的管理界面:展示全部员工信息,提供一个根据名称查询某个员工信息展示,添加员工信息,删除员工信息,修改员工信息。
5.2 流程图

需要创建的类:
- 启动类:APP
- 实体类:Employee User
- 界面类:EmployeeManagerUI EditEmployeeUI AddEmployeeUI
5.3 类图
从代码里查看
- APP:
- 在App类内部创建LoginUI类: App依赖LoginUI

-
LoginUI
- LoginUI类内部的login方法创建EmployeeManagerUI:LoginUI依赖EmployeeManagerUI

- LoginUI类拥有User实体类的对象成员变量且创建类时就同时初始化了:LoginUI组合User

-
EmployeeManagerUI类
- EmployeeManagerUI类的拥有AddEmployeeUI成员变量,并将自身引用传递过去:互相关联



- EmployeeManagerUI类同时拥有Employee成员变量:关联

- EmployeeManagerUI类的拥有AddEmployeeUI成员变量,并将自身引用传递过去:互相关联
-
EditEmployee类同AddEmployee类,最终类图结果如下,使用了IDEA的UML导出功能
