4. UML类图与设计模式表达:类、接口、继承、实现、依赖、关联、聚合、组合关系的UML表示
UML类图这东西,说白了就是程序员之间的「通用语言」。我刚开始带团队那会儿,发现很多人画图全凭感觉,箭头乱飞。结果呢?代码写出来跟设计图完全是两码事。嗯,今天我们就来把这几种关系彻底捋清楚。
4.1 类与接口的基本表示
先看最基础的。一个类在UML里就是个三格矩形:
- 第一格:类名(如果是抽象类,用斜体)
- 第二格:属性(字段)
- 第三格:方法(操作)
接口呢?顶部加个 <<interface>> 标记,或者用「棒棒糖表示法」——一个小圆圈加一条线。我个人习惯用前者,更直观。
访问修饰符符号:
+公开(public)-私有(private)#受保护(protected)~包内可见(package)
// 对应的Java代码
public class User {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public interface IUserService {
void save(User user);
User findById(int id);
}
4.2 继承与实现——「is-a」关系
继承用空心三角形 + 实线。实现用空心三角形 + 虚线。区别就一条线,但语义完全不同。
继承是「子类是父类的一种」。实现是「类遵守了接口的契约」。
我的经验: 我在项目中见过有人把接口实现画成继承箭头。结果新同事看了图,以为接口是个抽象类,直接在里面写字段。嗯,这种低级错误,画图时多看一眼就能避免。
// 继承
class Animal { }
class Dog extends Animal { }
// 实现
interface Flyable { }
class Bird implements Flyable { }
4.3 依赖关系——「uses-a」关系
依赖用虚线箭头。箭头指向被依赖方。这是最弱的关系——一个类临时用了另一个类。
什么时候算依赖?方法参数、局部变量、静态方法调用。说白了就是「用完就扔,不长期持有」。
避坑指南: 我曾经在重构一个支付模块时,发现类图里画了一大堆依赖箭头。仔细一看,全是把关联画成了依赖。结果代码里到处都是new对象,耦合得一塌糊涂。记住:依赖是临时的,关联是长期的。
public class OrderService {
// 依赖:方法参数
public void createOrder(Product product) {
// 依赖:局部变量
Logger logger = new Logger();
logger.log("创建订单");
}
}
4.4 关联关系——「has-a」关系
关联用实线箭头。表示一个类知道另一个类的存在,并且长期持有引用。最常见的体现就是成员变量。
关联可以标注多重性:
| 标记 | 含义 |
|---|---|
| 0..1 | 零或一个 |
| 1 | 恰好一个 |
| 0..* | 零到多个 |
| 1..* | 一到多个 |
| * | 任意多个 |
public class Customer {
// 关联:成员变量
private List<Order> orders; // 1..*
}
public class Order {
private Customer customer; // 1
}
4.5 聚合与组合——「整体-部分」关系
这两个最容易搞混。我当年面试时就被问倒过。你想想看:
- 聚合:空心菱形 + 实线。整体和部分可以分开存在。
- 组合:实心菱形 + 实线。整体和部分同生共死。
经典例子:
- 聚合:学校(School)和老师(Teacher)。学校没了,老师还在。
- 组合:房子(House)和房间(Room)。房子拆了,房间也没了。
// 聚合
public class School {
private List<Teacher> teachers; // 老师可以换学校
}
// 组合
public class House {
private List<Room> rooms; // 房间随房子一起销毁
public House() {
rooms = new ArrayList<>();
rooms.add(new Room());
}
}
4.6 关系强度对比
从弱到强排个序:
- 依赖:临时使用,最弱
- 关联:长期持有引用
- 聚合:整体-部分,生命周期独立
- 组合:整体-部分,生命周期绑定
- 继承/实现:最强耦合,慎用
我的建议: 画图时先画继承和实现,再画关联和聚合。依赖关系我一般最后才补,因为它是「运行时」才体现的,设计阶段不用太纠结。
4.7 知识体系总览
下面这张图把六种关系放在一起对比。我特意把箭头样式和语义对应起来,方便你记忆。
4.8 实战中的常见误区
最后说几个我踩过的坑:
- 滥用继承:我曾经设计一个「动物」继承体系,猫、狗、鸟都继承自动物。结果鸟需要飞,猫需要游泳,继承树越来越乱。后来全改成组合,清爽多了。
- 忽略多重性:关联关系不标数字,代码里用List还是Set?用数组还是集合?全靠猜。画图时顺手标上,省得后期扯皮。
- 把依赖当关联:一个类在方法里new了另一个类,就画个关联箭头。这是错的,应该用依赖。关联意味着「长期持有」,依赖只是「临时借用」。
记住: UML图是沟通工具,不是艺术品。画到别人能看懂就行,别追求完美。我见过有人花三天画一张图,结果代码写出来完全不一样。嗯,本末倒置了。
好了,这六种关系你掌握了吗?下次画类图时,对着这张表检查一遍,保证不出错。