UML用例图中类之间的关系:关联、聚合、组合、依赖、泛化、实现

这六种是 UML 类图的核心关系,用来描述不同类(或接口)之间的关联逻辑,核心区别在于「关系强度、生命周期绑定、代码实现方式」。

先明确核心判断逻辑

所有关系按「关联强度从弱到强」排序:依赖(use-a) < 关联(has-a 弱) < 聚合(has-a 中) < 组合(has-a 强) < 实现(like-a) < 泛化(is-a)

  • is-a:“是一种”(列如学生是一种人);
  • has-a:“包含 / 拥有”(列如班级拥有学生);
  • use-a:“临时使用”(列如学生用计算器做题);
  • like-a:“像一种(实现接口)”(列如麻雀实现 “会飞” 接口)。

一、泛化(Generalization):is-a 继承关系

核心定义:「子类是父类的一种」,子类继承父类的属性和方法,可扩展自己的功能。

是最强的关系,子类和父类是 “本质同一类事物”。

生活类比:

  • 学生(子类)是 人(父类)的一种;
  • 猫(子类)是 动物(父类)的一种。

UML 图示:

  • 实线 + 空心三角(三角指向「父类」)。

代码示例(Java):

java

// 父类:人
class Person {
    String name;
    void eat() {}
}

// 子类:学生(泛化自Person)
class Student extends Person { // extends 表明泛化
    String studentId; // 子类扩展属性
    void study() {} // 子类扩展方法
}

关键特点:

  • 子类完全继承父类非私有成员,可重写父类方法;
  • 父类变,子类可能受影响(耦合度高);
  • 只能单继承(Java 中),但可多层继承(学生→人→生物)。

二、实现(Realization):like-a 接口实现

核心定义:「类实现接口的所有抽象方法」,接口定义 “行为规范”,类负责具体实现。

和泛化逻辑类似,但接口是 “行为契约”,不是具体类。

生活类比:

  • 麻雀(类)实现 会飞(接口)的行为;
  • 计算器(类)实现 可计算(接口)的行为。

UML 图示:

  • 虚线 + 空心三角(三角指向「接口」)。

代码示例(Java):

java

// 接口:定义行为规范(无具体实现)
interface Flyable {
    void fly(); // 抽象方法
}

// 类:实现接口(必须重写所有抽象方法)
class Sparrow implements Flyable { // implements 表明实现
    @Override
    public void fly() {
        System.out.println("麻雀扇动翅膀飞");
    }
}

关键特点:

  • 接口只定义方法签名,不存属性(Java 8 + 可有默认方法);
  • 一个类可实现多个接口(解耦,弥补单继承局限);
  • 接口变,所有实现类都要改(契约约束)。

三、关联(Association):has-a 普通关联

核心定义:「两个类之间的 “一般联系”」,相互知道对方的存在,可双向或单向访问。

关系强度中等,是最常用的 “拥有” 关系,列如 “学生和老师”“用户和订单”。

生活类比:

  • 学生 和 老师(学生知道自己的老师,老师知道自己的学生);
  • 用户 和 订单(用户拥有多个订单,订单属于一个用户)。

UML 图示:

  • 实线(双向关联无箭头;单向关联箭头指向「被关联方」);
  • 可标注 “multiplicity”(数量关系,列如 1 对多、多对多)。

代码示例(Java):

java

// 订单类
class Order {
    String orderId;
}

// 用户类(关联Order:用户拥有多个订单)
class User {
    String userId;
    List<Order> orders; // 成员变量引用Order,体现关联关系
}

关键特点:

  • 关联是「长期持有」(用户和订单的关系会持续存在);
  • 可双向关联(Order 类也加User user),或单向关联(只 User 有 Order,Order 不知道 User);
  • 多对多关联需用中间类(列如 “学生和课程”,用 “选课表” 中间类)。

四、聚合(Aggregation):has-a 松散包含

核心定义:「整体包含部分,但部分可独立于整体存在」,是 “弱拥有” 关系。

整体和部分是 “组装关系”,部分能脱离整体单独存活。

生活类比:

  • 班级(整体) 和 学生(部分):学生离开班级,依然是学生;
  • 电脑(整体) 和 鼠标(部分):鼠标拆下来,还能给其他电脑用。

UML 图示:

  • 空心菱形 + 实线(菱形在「整体端」,指向部分);
  • 菱形是 “容器” 的象征,空心表明 “部分可独立”。

代码示例(Java):

java

// 部分类:学生
class Student {
    String studentId;
}

// 整体类:班级(聚合学生)
class Class {
    String className;
    List<Student> students;

    // 部分(学生)通过构造方法传入,不是在整体内部创建
    public Class(List<Student> students) {
        this.students = students;
    }
}

// 使用:学生可先创建,再加入班级;班级解散,学生还在
Student s1 = new Student("1001");
List<Student> students = Arrays.asList(s1);
Class cls = new Class(students);

关键特点:

  • 整体和部分「生命周期独立」(部分可提前创建,整体销毁后部分仍存在);
  • 部分可被多个整体共享(一个学生可临时加入多个兴趣班);
  • 代码上:部分通过 “外部传入”(构造器 /setter),不是整体内部new出来的。

五、组合(Composition):has-a 强依赖包含

核心定义:「整体包含部分,部分和整体同生共死」,是 “强拥有” 关系。

整体和部分是 “不可分割” 的,部分不能脱离整体单独存在。

生活类比:

  • 人(整体) 和 心脏(部分):心脏离开人就失去意义,人死亡心脏也没用;
  • 订单(整体) 和 订单明细(部分):订单明细不能脱离订单单独存在。

UML 图示:

  • 实心菱形 + 实线(菱形在「整体端」,指向部分);
  • 实心表明 “部分不可独立”。

代码示例(Java):

java

// 部分类:订单明细
class OrderItem {
    String productName;
    int quantity;
}

// 整体类:订单(组合订单明细)
class Order {
    String orderId;
    List<OrderItem> orderItems;

    // 部分(订单明细)在整体内部创建,不是外部传入
    public Order() {
        this.orderItems = new ArrayList<>();
        this.orderItems.add(new OrderItem()); // 整体创建部分
    }
}

// 使用:创建订单时自动创建明细;删除订单,明细也跟着销毁
Order order = new Order();

关键特点:

  • 整体和部分「生命周期绑定」(整体创建→部分创建,整体销毁→部分销毁);
  • 部分只能属于一个整体(一个心脏不能同时属于两个人);
  • 代码上:部分在整体的构造方法中new出来,外部无法直接创建部分并传入。

六、依赖(Dependency):use-a 临时使用

核心定义:「一个类临时使用另一个类的功能,不长期持有」,是最弱的关系。

列如 “学生用计算器做题”“工具类被其他类调用”,用完就断联。

生活类比:

  • 学生(依赖方) 和 计算器(被依赖方):学生做题时用计算器,做完就不用了;
  • 程序员(依赖方) 和 IDE(被依赖方):写代码时用 IDE,不写代码时 IDE 可关闭。

UML 图示:

  • 虚线 + 箭头(箭头指向「被依赖方」,表明 “谁依赖谁”)。

代码示例(Java):

java

// 被依赖类:计算器
class Calculator {
    static int add(int a, int b) { // 静态方法,方便临时调用
        return a + b;
    }
}

// 依赖方:学生
class Student {
    // 依赖方式1:方法参数(临时传入)
    void doHomework(Calculator calculator) {
        int result = calculator.add(1, 2);
    }

    // 依赖方式2:局部变量(临时创建)
    void doMath() {
        Calculator calc = new Calculator(); // 局部变量,方法结束后销毁
        calc.add(3, 4);
    }

    // 依赖方式3:静态方法调用(不用创建对象)
    void doSum() {
        Calculator.add(5, 6);
    }
}

关键特点:

  • 「临时使用,不长期持有」:被依赖类不会作为依赖类的成员变量(区别于关联);
  • 依赖方变化不会影响被依赖方(列如学生换计算器,计算器本身不用改);
  • 代码上:被依赖类以「方法参数、局部变量、静态方法调用」的形式出现,不是成员变量。

七、核心区别对比表(快速区分)

关系类型

核心关系

生命周期

代码表现(Java)

关键判断点

泛化

is-a(继承)

子类依赖父类

extends 关键字

子类是父类的一种

实现

like-a(接口)

实现类依赖接口

implements 关键字

类实现接口的方法

关联

has-a(普通拥有)

相互独立(长期持有)

成员变量引用

两个类长期关联(列如用户 – 订单)

聚合

has-a(松散包含)

部分独立于整体

构造器 /setter 传入部分

部分可脱离整体存活(班级 – 学生)

组合

has-a(强包含)

部分与整体同生共死

整体内部 new 部分

部分不能脱离整体(人 – 心脏)

依赖

use-a(临时使用)

临时关联

方法参数 / 局部变量 / 静态调用

不长期持有,用完即断(学生 – 计算器)

记忆:

  • 是 “一种” 东西 → 泛化(继承);
  • 要 “实现” 某个功能 → 实现(接口);
  • 长期 “拥有” 另一个东西 → 关联;
  • 松散 “包含”(可拆分) → 聚合;
  • 紧密 “包含”(不可拆分) → 组合;
  • 临时 “用一下” → 依赖。
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
黄傻白的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容