uml 类图学习

uml 类图学习

前言

最近在想写一些代码文档的时候发现这方面的知识有所欠缺,没有相关的工具确实没办法梳理某些工程项目的脉络

遂针对 uml 及其在 markdown 中的使用记录一些笔记

简介

uml 全称是 unified modeling language 这是包含各类图表的标准化语言,主要用于帮助软件工程师识别,构建某些软件系统

他的主要识别对象为 面向对象语言 通过大量的图形描述软件系统的设计, 其主要可以又如下分类

  1. 类图
  2. 组件图
    等等

具体分类参考what is uml

针对上述分类,将详细讲讲上述图的详情

类图

首先,类图是 静态结构图表 的一种,主要用于描述软件系统中的类/成员/方法以及其与其他类的关系

类图的图形包含以下要素

  1. 描述类的图形集合
  2. 描述类与类之间关系的集合

类的图形集合

首先类的图形包含以下要素

  1. 类名
  2. 类的成员
  3. 类的方法

一个基本的类如下图所示

--- title:MyClass --- classDiagram class MyClass{ +int attribute1 -float attribute2 #Circle attribute3 +op1(boolean p1) string -op2(int p3) float #op3() Class6 }

MyClass 这个类有三个成员及三个方法,其中

  1. op1 方法返回的是 string 类型,op3返回的是 Class6 类型
  2. + 表示为公共成员或方法, - 表示为私有成员或方法, # 表示为保护成员及方法, ~ 表示为包里的成员及方法

类之间的关系

前言:类与类之间的关系有如下几大类

  1. 依赖关系 (Dependency) Use-A

    • 一个类使用另一个类,但不持有其引用
  2. 关联关系 (Association) Has-A

    • 一个类持有另一个类的引用

    • 包括以下特殊形式:

      a. 聚合关系 (Aggregation)

      • 整体与部分的关系,部分可以独立存在

      b. 组合关系 (Composition)

      • 更强的整体与部分的关系,部分不能独立存在
  3. 继承关系 (Inheritance) Is-A

    • 一个类是另一个类的子类
  4. 实现关系 (Realization)

    • 一个类实现一个接口

接下来,针对上文三种基本关系进行讨论

依赖关系

依赖关系代表两个类是 Use-A 关系,简称 A use a B 也就是 A使用了B 代码如下所示

class Car {
    public void carStartDemo(){
        Engine audiEngine = new Engine();
        audiEngine.start();
        audiEngine.stop();
    }
}
class Engine {
    public void start(){
        System.out.println("start your engine");
    }
    public void stop(){
        System.out.println("stop your engine");
    }
}

uml 图如下所示

classDiagram class Car { +void carStartDemo() void } class Engine { +start() void +stop() void } Car ..> Engine : used in CarStartDemo()

其核心为, Car 类的 CarStartDemo() 方法只是使用了 Engine 类,Engine 类并没有作为 Car 的成员存在,所以两类的关系为 依赖关系

除了上述依赖形式(局部变量依赖),依赖关系还可以通过其他方式体现,如

  1. 方法参数依赖:一个类的方法接受另一个类的对象作为参数
  2. 返回型依赖:一个类的方法返回另一个类的对象
  3. 局部变量依赖:在方法中创建另一个类的对象作为局部变量
  4. 静态方法调用:调用另一个类的静态方法

其代码如下所示

class Car{
    //方法参数依赖
    public void StartEngine(Engine engine){
        engine.Start();
    }
    //返回型依赖
    public Engine createEngine(){
        return new Engine();
    }
    //局部变量依赖
    public void carStartDemo(){
        Engine audiEngine = new Engine();
        audiEngine.start();
    }
    //静态方法调用
    public void checkEngineStatus(){
        boolean is Running = Engine.isRunning();
    }
}
class Engine{
    public void Start(){
        System.out.println("start your engine");
    }
    public static isRunning(){
        return true;
    }
}

注意:依赖关系 代表两个类之间的 生命周期 并不严格互相依赖

  1. 方法参数依赖的生命周期主要看调用者的使用
  2. 返回型的生命周期主要看调用者使用
  3. 局部变量的生命周期主要看方法执行期间
  4. 静态方法调用的生命周期:(压根没有创建实例,没有生命周期可言)

关联关系

关联关系代表两个类是 Has-A 关系,简称 A has a B 代码如下所示

import java.util.ArrayList;
import java.util.LInkedList;
import java.util.List;

public enum VehicleType{
    TRUCK,
    BICYCLE,
    SUV
}
// 使用泛型
// 使用重载
public class Car<T extends List<Product>>{
    private final VehicleType type;
    private final Engine engine;
    private final T products;
    
    public Car(VehicleType type){
      this(type, (T) new ArrayList<>());// 针对 ArrayList 类型做了类型转换
    }
    
    public Car(VehicleType type,T products){
      this.type = type;
      this.engine = new Engine();
      this.products = products;
    }

    public void carStartDemo{
        this.engine.Start();
    }
}
public class Engine{
    public void Start(){
        System.out.println("start your engine");
    }
}
public record Product(String name,int weight){
    @Override
    public String toString(){
      return String.format("Product: name is %s,weight is %d",name,weight);
    }
    
    public void printDetails(){
      System.out.printf("Product Details: name os %s, weight is %d kg%n",name,weight);
    }
};

uml 图如下图所示

classDiagram class Car~T~{ -VehicleType type -Engine engine -T products +Car(VehicleType type) +Car(VehicleType type, T product) +CarStartDemo() void } class Engine{ +start() void } class VehicleType{ <<enumeration>> TRUCK BICYCLE SUV } class Product{ <<record>> +String name +int weight +String toString() void +prinDetails() void } Car *-- Engine: contains Car --> VehicleType: has Car o-- "0..*" Product: has

上述代码覆盖了关联,聚合及组合关系

  1. 关联关系 (association),指代 CarVehicleType 的关系,在上述代码中,VehicleType 实际上是一个预定义常量,其生命周期并不由 Car 类掌握 VehicleType 的生命周期,所以说 Car has a VehicleType
  2. 聚合关系 (aggregation),指代 CarProduct 的关系,在上述代码中,ProductList 的形式作为 Car 类型的成员变量,其中 Car 并不掌握 Product 的生命周期,所以说 Car has many Product
  3. 组合关系 (composition),指代 CarEngine 的关系,在上述代码中,Engine 的实例由 Car 创建,Engine 的生命周期依赖于 Car ,所以说 Car contains Engine

以上三者的关系如下图所示

三者的关系

更详细的代码,解释可以参考 association-composition-aggregation-java 这篇文章

而在 uml 图中,三者可以这样表示

Type Description
--> association
--o aggregation
--* composition

继承关系

继承关系代表两个类是 Is-A 关系, 简称 A is a B 也就是 B是A的父类 代码如下所示

public enum DriveMode {
  MANPOWER,
  GASOLINE,
  NATURAL_GAS,
}
public abstract class Vehicle{
  private final int horsePower;
  private final int carryingCapacity;

  public Vehicle(int horsePower,int carryingCapacity){
    this.horsePower = horsePower;
    this.carryingCapacity = carryingCapacity;
  }
  public integer getHorsePower(){return this.horsePower};
  public integer getCarryingCapacity(){return this.carryingCapacity};

  @Override
  public String toString(){
    return String.format("the vehicle's horsePower is %d,carrying capacity is %d",this.horsePower,this.carryingCapacity);
  }
}
// 新建另外一个抽象类,用于做 Car 类 以及 Bike 类的高级抽象( Car 与 Bike 两个类的构造方法很类似 )
public abstract class BrandedVehicle extends Vehicle{
  private final String brandName;
  private final DriveMode driveMode;

  public BrandedVehicle(int horsePoser,int carryingCapacity,String brandName,DriveMode driveMode){
    super(horsePower,carryingCapacity); // 调用 Vehicle 的构造函数
    this.brandName = brandName;
    this.driveMode = driveMode;
  }

  public String getBrandName(){return this.brandName};
  public DriveMode getDriveMode(){return this.driveMode};

  @Override
  public String toString(){
    return String.format("%s: brandName=%s, driveMode=%s, %s", 
            this.getClass().getSimpleName(), this.brandName, this.driveMode, super.toString());
  }
  // this.getClass().getSimpleName() 这个函数的作用在于返回当前对象的雷鸣
  // this.getClass() 这个函数获取当前对象的运行时类
  // .getSimpleName() 返回该类的简单名称
  // 使用这个函数就可以避免在 Car 类以及 Bike 类中重新定义 toString 方法了,无需硬编码了
}
public class Car extends BrandedVehicle {

    // 该函数调用同类的四参数构造函数,也就是下面的那个构造函数,简化写法,这种写法叫 *构造函数链接* (constructor chaining)
    public Car(int horsePower, int carryingCapacity, String brandName) {
        this(horsePower, carryingCapacity, brandName, DriveMode.GASOLINE);
    }

    public Car(int horsePower, int carryingCapacity, String brandName, DriveMode driveMode) {
        // 调用父类构造函数
        super(horsePower, carryingCapacity, brandName, driveMode);
    }
}

public class Bike extends BrandedVehicle {
    public Bike(int horsePower, int carryingCapacity, String brandName) {
        this(horsePower, carryingCapacity, brandName, DriveMode.MANPOWER);
    }

    public Bike(int horsePower, int carryingCapacity, String brandName, DriveMode driveMode) {
        super(horsePower, carryingCapacity, brandName, driveMode);
    }
}

public class DiyVehicle extends Vehicle{
    private final DriveMode driveMode;
    private final String author;

    public DiyVehicle(int horsePower, int carryingCapacity, DriveMode driveMode, String author){
        super(horsePoser, carryingCapacity);
        this.DriveMode = driveMode;
        this.author = author;
    }
    
    @Override
    public String toString(){
        return String.format("%s: author=%s, driveMode=%s, %s",
            this.getClass().getSimpleName(), this.author, this.driveMode, super.toString());
    }
}

uml 图如下所示

classDiagram class Vehicle{ <<abstract>> -int horsePower -int carryingCapacity +Vehicle(int horsePower,int carryingCapacity) +getHorsePower() int +getCarryingCapacity() int } class DriveMode{ <<enumeration>> MANPOWER GASOLINE NATURAL_GAS } class BrandedVehicle{ <<abstract>> -String brandName -DriveMode driveMode +BrandedVehicle(int horsePower,int carryingCapacity,String brandName,DriveMode driveMode) +getBrandName() String +getDriveMode() DriveMode +toString() String } class Car{ +Car(int horsePower,int carryingCapacity,String brandName) +Car(int horsePower,int carryingCapacity,String brandName,DriveMode driveMode) } class Bike{ +Bike(int horsePower,int carryingCapacity,String brandName) +Bike(int horsePower,int carryingCapacity,String brandName,DriveMode driveMode) } class DiyVehicle{ -DriveMode driveMode -String author +DiyVehicle(int horsePower,int carryingCapacity,DriveMode driveMode,String author) +toString() String } BrandedVehicle --|> Vehicle BrandedVehicle --> DriveMode Car --|> BrandedVehicle Bike --|> BrandedVehicle DiyVehicle --|> Vehicle Car --> DriveMode Bike --> DriveMode

在上述代码中,共涉及到 Vehicle BrandedVehicle DiyVehicle Car Bike DriveMode 这6个类

可以看到 Vehicle BrandedVehicle DiyVehicle Car Bike 这几个类互相为继承关系,具体关系还是看上图

同时,我们可以说 BrandedVehicle is a Vehicle, Car is a BrandedVehicle

BrandedVehicle Car BikeDriveMode 类则是关联关系

实现关系

前文已经提出,实现关系是指 一个类实现了另一个接口 下文是示例代码

interface Vehicle{

  // 隐式 public void 
  void start();

  // 隐式 public void
  void stop();

  // 隐式 public String
  String getDescription();

  // 默认方法,并不需要实现
  // 隐式 public
  default void honk(){
    System.out.println("be be");
  }

  // 静态方法,并不需要实现
  static boolean isVehicle(Object obj){
    return obj instanceof Vehicle;
  }

  default void performSafetyCheck(){
    if(checkEngine() && checkBrakes()){
      System.out.println("Vehicle is safe to drive");
    }else{
      System.out.println("Vehicle needs maintenance");
    }
  }

  // 私有方法,实现类无法直接访问,只能间接通过 default 方法 *performSafetyCheck* 进行调用访问
  private boolean checkEngine(){
    // ... 一些比较复杂的检查逻辑
    System.out.println("Checking engine");
    return true;
  }

  // 接口里的私有方法与类中的私有方法较为类似
  private boolean checkBrakes(){
    // ... 一些比较复杂的检查逻辑
    System.out.println("Checking brakes");
    return true;
  }
} 
Class Car implements Vehicle{
  private String band;

  public Car(String brand){
    this.brand = brand;
  }

  @Override
  public void start(){
    System.out.println(brand + " car is starting");
  }

  @Override
  public void stop(){
    System.out.println(brand + " car has stop");
  }

  @Override
  public String getDescription(){
    return "This is " + brand + " 's car";
  }
}

uml 类图如下所示

classDiagram class Vehicle{ <<interface>> +start() void +stop() void +honk() void +isVehicle(Object) boolean +performSafetyCheck() void -checkEngine() boolean -checkBrakes() boolean } class Car{ -String band +Car(String) +start() void +stop() void +getDescription() String } Car ..|> Vehicle

在上述代码中,我们可以说 Car 类实现了 Vehicle 接口

Q:什么时候该用 继承关系 什么时候该用 关联关系

A:主要还是看是打算共用哪些内容. 如果说共用的是一些行为,那么用接口比较合适。而如果共用的是父类的大多数功能,那么就还是要使用继承关系,也就是当需要共享状态的时候就使用继承

总结

上文总共从 类的定义类与类之间的关系 两个大方面展示了 uml 图该如何画

有一些比较重要的点,需要关注

  1. 依赖关系与关联关系的区别(生命周期)
  2. 关联关系三兄弟之间的区别
  3. 什么时候该用继承关系,什么时候该用实现关系(代码具体实践)

组件图

前言

组件图通过抽象系统中的功能组件,描述大型软件工程中的各组件(功能)之间的关系。达到可视化记录工程结构的目的,其本质是类图,但是区别在于抽象的层级不同,组件图是系统架构层面的图表,类图则关心功能类的具体实现

具体内容

让我们举一个与汽车有关的例子

  1. 涉及对象方面
  • 汽车本身
  • 操控组件
  • 动力组件
  • 能源组件
  • 用户(外部actor)
  • 外部能源(外部系统)
  1. 接口方面
  • 操控组件 对外提供端口(方向盘)
  • 动力组件 对外提供端口(油门,刹车)
  • 能源组件 对外提供端口 能源补充端口
  • 能源组件 对内提供接口 燃料供应接口
  1. 关系方面
  • 用户 通过端口(方向盘)使用 操控组件 提供的服务
  • 用户 通过端口(油门,刹车)使用 动力组件 提供的服务
  • 动力组件 通过接口(燃料供应端口)使用 能源组件 提供的服务
  • 能源组件 通过端口(燃料供应接口)使用 外部能源 提供的服务

uml 图如下图所示

组件图概览

上述图中有一些核心要素,如下图所示

组件图

总结

本篇文章详细介绍了如何画 类图组件图

还有一些比较重要的如 用例图 序列图 下一篇章节再讲

posted @ 2025-01-31 16:56  五花肉炒河粉  阅读(22)  评论(0)    收藏  举报