你一定要会的 UML 知识

447 阅读6分钟

为什么要使用 UML?

许多其他行业(如电子、化工、建筑、音乐等)都有特有的表示法,表示人们创造出的制品。同样,人们用 UML 对构建的系统进行建模(即表示)。UML 有几种不同类型的图,每一种都提供了系统的某种视图。

Kleyn和Gingrich指出:“开发者必须理解对象所涉及的结构和功能。开发者必须理解类对象的分类结构、使用的继承机制、对象独立的行为以及整体系统的动态行为。这个问题有点类似观看网球或足球这样的体育赛事——每个摄像机都揭示了动作的一个方面,这不能由一个摄像机独立完成。”

UML 分类

UML 图可以分成两大类:结构图和行为图。系统的复杂性既来自于系统中元素的数量和组织(即结构),也来自于这些元素协作完成其功能的方式(即行为)。

结构图

这些图用于展示系统中元素的静态结构。它们描述系统的架构组织、系统的物理元素、系统的运行时刻配置和业务中领域相关的元素等。UML 结构图包括:

  • 包图
  • 类图
  • 组件图
  • 部署图
  • 对象图
  • 组合结构图

行为图

在所有的软件系统中,事件都是动态发生的:对象被创建和销毁,对象之间按次序发出消息,而且在某些系统中,外部的事件会触发某些对象上的操作。利用下面这些图来描述问题的动态行为语义或它的实现:

  • 用例图
  • 活动图
  • 状态图
  • 交互图
  • 序列图
  • 通信图
  • 交互概述图
  • 时间图

下面将详细介绍三种可以在掘金编辑器中使用的 UML 图,类图、序列图、状态图。

掘金编辑器中 UML 图是使用 mermaid 实现的,mermaid 是一个非常棒的制图工具,你可以在它的官网了解更多。

image.png

类图(Class diagrams)

类图用于表示类和它们之间的关系。类图中有两个基本元素,即类和它们的基本关系。

类表示法

类表示由三个部分组成:类图标由三个部分组成:第一个部分放置类名,第二个部分放置属性,第三个部分放置操作。

classDiagram
    class BankAccount
    BankAccount : +String owner
    BankAccount : +Bigdecimal balance
    BankAccount : +deposit(amount)
    BankAccount : +withdrawal(amount)

类关系

类很少是独立的,相反,它们会通过不同的方式与其他类协作。类之间的基本联系包括关联、泛化、聚合和组合。

关联

关联标连接了两个类,体现了一种语义联系。

classDiagram
    PlanAnalyst "+staff *" --> "+lead 0..1" PlanAnalyst
    PlanAnalyst "1..2" --> "1..*" PlanMetrics: Analyzes

泛化

泛化的箭头指向超类,关联的另一端是子类。

classDiagram
    Plan <|-- GrowingPlan
    Plan <|-- GardeningPlan
    GrowingPlan <|-- FruitGrowingPlan

聚合

关联体现了“组成部分”关系,它是一般的关联关系的一种约束形式。而聚合表明一种整体/部分层次结构,也意味着能够从聚合体导航到它的部分。它的空心菱形一端是聚合体(整体),另一端的类代表它的实例构成了聚合对象的部分。

classDiagram
    EnviromentalContainer o-- Light
    EnviromentalContainer o-- Heater
    EnviromentalContainer o-- Cooler

组合

选择聚合通常是分析或架构设计时的决定,选择组合(物理包容)通常是具体的、战术上的问题。区分物理包容是很重要的,因为在构建和销毁聚合体的部分时,它的语义会起作用。组合图标表示一种包容关系,表现为带有一个实心菱形的关联,菱形所在的一端是整体。在这一端的多重性是1,因为根据定义,部分在整体之外就没有任何意义,整体拥有部分,部分的生命周期与整体是一样的。

classDiagram
    CropHistory "*" o-- "1" Crop
    CropHistory "1" *-- "*" ClimateEvent
    CropHistory "1" *-- "*" NutrientSchedule

序列图(Sequence diagrams)

序列图用于跟踪在同一个上下文环境中一个场景的执行。使用序列图的好处在于比较容易读出消息传递的相对次序。

生命线与消息

在序列图中,我们感兴趣的实体(与对象图中的对象一样)被水平地放在图的顶部。垂直的虚线称为“生命线”,画在每个对象下面,表示对象的存在。

sequenceDiagram
    PlanAnalyst->>PlanMetrics: timeToHarvest(C)

    activate PlanAnalyst
 
    activate PlanMetrics
    PlanMetrics->>GardeningPlan: status()
    activate GardeningPlan
    GardeningPlan->>GrainCrop: maturationTime()
    activate GrainCrop
    deactivate GrainCrop
    deactivate GardeningPlan
    deactivate PlanMetrics
    
    PlanAnalyst->>GrainCrop: yield()
    activate GrainCrop
    GrainCrop->>Crop: yield()
    deactivate GrainCrop
    activate Crop
    deactivate Crop
   
    PlanAnalyst->>PlanAnalyst: netCost(C)
    activate PlanAnalyst
    deactivate PlanAnalyst
 
    deactivate PlanAnalyst

消息被画成水平的。消息图标的端点与垂直线相连,这些垂直线又与图顶部的实体相连。消息从发出者指向接收者。次序由垂直位置来表示,第一个消息出现在图的顶部,最后一个消息出现在图的底部。因此,就不需要顺序编号了。

消息的表示法(如线的类型和箭头的类型)说明了消息的类型。

sequenceDiagram
    Object1->>Object2: 同步消息
    activate Object1
    activate Object2
    deactivate Object2
    Object1-)+Object2: 异步消息
    Object2-->>-Object1: 返回消息
    deactivate Object1

控制结构

循环

可以在序列图中表达循环。

sequenceDiagram
    Alice->John: Hello John, how are you?
    loop Every minute
        John-->Alice: Great!
    end

选择

可以在序列图中表示选择路径。

sequenceDiagram
    Alice->>Bob: Hello Bob, how are you?
    alt is sick
        Bob->>Alice: Not so good :(
    else is well
        Bob->>Alice: Feeling fresh like a daisy
    end
    opt Extra response
        Bob->>Alice: Thanks for asking
    end

状态图(State diagrams)

状态图将行为表示为一系列的状态转换,由事件触发,并与可能发生的动作相关联。状态图通常用于描述单个对象的行为。

状态图的两个基本元素是状态和状态转换。

初始状态、最终状态和简单状态

对象的状态代表了它的行为的累积结果。在任何给定的时间点,对象的状态包含了它的所有特性(通常是静态的),以及这些属性当前的值(通常是动态的)。所谓特性,指的是这个对象的所有属性和它与其他对象的关系。

当一个对象处于指定状态时,它可以做下面的事情:

  • 执行一项活动;
  • 等待一个事件;
  • 完成一个条件;
  • 做上面的几件事情或全部事情。

在每个状态图中,必须只有一个默认的初始状态。通常,也需要设计一个结束状态。

stateDiagram-v2
    [*] --> Initializing
    Initializing --> Timing
    Timing --> [*]

转换与事件

状态之间的移动称为“转换”。在状态图中,转换表示为状态之间的有向箭头。每个状态转换连接两个状态。

stateDiagram-v2 s1 --> s2: 转换

有多种不同的方式可以控制触发一次转换。没有标注的转换称为“完成转换”。这就是意味着,当源状态完成时,这个转换就会被自动触发,然后进入目标状态。

在其他情况下,必须发生某些事件才能触发转换,这样的事件标注在转换上。

stateDiagram-v2
    [*] --> Initializing
    Initializing --> Timing
    Timing --> Paused: pause
    Paused --> Timing: resume
    Timing --> [*]

控制转换

不仅可以使用事件控制转换,也可以通过指定条件来控制转换。这些条件起到了监护的作用,当事件发生时,条件将决定允许转换发生(如果条件为真)或不允许转换发生(如果条件为假)。

stateDiagram-v2
    SoundingAlarm: Sounding Alarm

    [*] --> Initializing
    Initializing --> Timing
    Timing --> Paused: pause
    Paused --> Timing: resume
    Timing --> SoundingAlarm: [duration > maintenance time]
    SoundingAlarm --> Timing: clear [cooler online] / set duration = 0
    Timing --> [*]

复合状态与嵌套状态

在更大、更复杂的系统中,状态图会变得很大,纠缠在一起,变得不实用。嵌套状态的功能让状态图有了深度,这是状态图的一个主要功能,可以缓解复杂系统中状态和状态转换产生组合爆炸的情况。

stateDiagram-v2
    SoundingAlarm: Sounding Alarm

    [*] --> Initializing
    Initializing --> Timing

    state Operating {
        Timing --> Paused: pause
        Paused --> Timing: resume
        Timing --> SoundingAlarm: [duration > maintenance time]
        SoundingAlarm --> Timing: clear [cooler online] / set duration = 0
    }
    Timing --> [*]

并发控制

并发行为可以用状态图来描述,只要将复合状态用虚线分成两个或多个子区域就可以了。复合状态中的每个子区域表示并发发生的行为。

stateDiagram-v2
    [*] --> MyCompositeState

    state MyCompositeState {
        [*] --> A
        A --> [*]
        --
        [*] --> B
        B --> D
        D --> [*]
        --
        [*] --> C
        C --> [*]
    }

参考

  • Mermaid
  • 面向对象分析与设计(第三版)格雷迪·布奇