2、UML类图详解:类、接口、继承、实现、关联、聚合、组合、依赖关系的UML表示
UML类图,说白了就是程序员之间的“建筑图纸”。
我刚入行那会儿,觉得画图浪费时间。代码能跑就行,画什么图?直到有一次接手一个老项目,光理清类之间的关系就花了两天。嗯,从那以后,我老老实实把UML捡起来了。
今天咱们就把UML类图中最核心的几种关系捋一遍。你想想看,搞懂了这些,看设计模式就跟看连环画一样简单。
2.1 类与接口的表示
先说说最基础的——类和接口长什么样。
类的表示
一个类用矩形框表示,分三层:
- 第一层:类名(如果是抽象类,用斜体)
- 第二层:属性(字段)
- 第三层:方法(操作)
符号说明:
+表示 public-表示 private#表示 protected~表示 package/default- 下划线表示静态成员
// 对应的Java代码
public class User {
private Long id;
public String name;
protected int age;
public void login() { }
private void encryptPassword() { }
}
接口的表示
接口也是矩形框,但顶部加了个 <<interface>> 的构造型标记。或者你也可以在类名上方画个小圆圈(棒棒糖表示法)。我个人习惯用前者,更直观。
// 对应的Java代码
public interface UserService {
void save(User user);
User findById(Long id);
}
2.2 继承与实现
这两个关系最常用,也最容易混淆。
继承(泛化)
用空心三角形 + 实线表示。三角形指向父类。
说白了就是“is a”的关系。猫是动物,学生是人。
// 父类
public class Animal {
public void eat() { }
}
// 子类继承父类
public class Dog extends Animal {
public void bark() { }
}
实现
用空心三角形 + 虚线表示。三角形指向接口。
这就是“像契约一样”的关系——我承诺实现你定义的行为。
// 接口
public interface Flyable {
void fly();
}
// 类实现接口
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔");
}
}
2.3 关联关系
关联是UML里最常用的关系。用实线箭头表示,箭头指向被关联的一方。
说白了就是“A知道B的存在”。比如一个老师知道他的学生是谁。
public class Teacher {
private List<Student> students; // 老师关联了学生
public void teach() { }
}
public class Student {
private String name;
}
关联还可以带方向:
- 单向关联:一个箭头,A→B
- 双向关联:两个箭头,或者没有箭头的一条实线
- 自关联:自己指向自己(比如树节点)
还可以标注多重性:
| 表示 | 含义 |
|---|---|
| 0..1 | 零或一个 |
| 1 | 恰好一个 |
| 0..* | 零或多个 |
| 1..* | 一或多个 |
| * | 任意多个 |
2.4 聚合与组合
这两个是关联关系的“加强版”,也是面试最爱问的。
聚合
用空心菱形 + 实线箭头表示。菱形在整体那一端。
聚合是“has a”的关系,但整体和部分生命周期不同。部分可以脱离整体独立存在。
举个例子:一个班级有多个学生。班级解散了,学生还在,可以转到别的班。
public class ClassRoom {
private List<Student> students; // 聚合关系
public ClassRoom(List<Student> students) {
this.students = students;
}
}
public class Student {
// 学生可以独立存在
}
组合
用实心菱形 + 实线箭头表示。菱形在整体那一端。
组合是更强的“has a”关系,整体和部分生命周期相同。部分不能脱离整体独立存在。
我记得有一次重构代码,发现有人把组合用成了聚合,结果对象销毁时内存泄漏了。嗯,这个坑我踩过。
public class House {
private Room room; // 组合关系
public House() {
this.room = new Room(); // 房间随房子一起创建
}
// 房子销毁时,房间也跟着销毁
}
public class Room {
// 房间不能脱离房子独立存在
}
2.5 依赖关系
依赖是最弱的关系。用虚线箭头表示,箭头指向被依赖的一方。
依赖说白了就是“临时用一下”。A的方法里用到了B,但A并不长期持有B的引用。
public class Driver {
public void drive(Car car) { // 依赖关系:方法参数
car.run();
}
}
public class Car {
public void run() { }
}
依赖的常见场景:
- 方法参数
- 局部变量
- 静态方法调用
- 返回值类型
2.6 知识体系总览
下面这张图把UML类图的几种关系串起来了。我建议你保存下来,写代码前瞄一眼。
2.7 实战建议
说了这么多理论,来点实际的。
怎么判断用哪种关系?
我一般问自己三个问题:
- 是不是“is a”? → 继承或实现
- 是不是“has a”? → 关联(再细问:生命周期是否同步?同步→组合,不同步→聚合)
- 是不是“uses a”? → 依赖
画图的时机
我个人习惯在写代码前先画类图。不用画太细,把核心类和关系理清楚就行。画完图再写代码,思路清晰很多,重构次数也少了。
好了,UML类图的关系就这些。你想想看,是不是比想象中简单?下次看设计模式的时候,先看类图,再看代码,事半功倍。
公众号:蓝海资料掘金营,微信deep3321