为什么要使用 UML?
许多其他行业(如电子、化工、建筑、音乐等)都有特有的表示法,表示人们创造出的制品。同样,人们用 UML 对构建的系统进行建模(即表示)。UML 有几种不同类型的图,每一种都提供了系统的某种视图。
Kleyn和Gingrich指出:“开发者必须理解对象所涉及的结构和功能。开发者必须理解类对象的分类结构、使用的继承机制、对象独立的行为以及整体系统的动态行为。这个问题有点类似观看网球或足球这样的体育赛事——每个摄像机都揭示了动作的一个方面,这不能由一个摄像机独立完成。”
UML 分类
UML 图可以分成两大类:结构图和行为图。系统的复杂性既来自于系统中元素的数量和组织(即结构),也来自于这些元素协作完成其功能的方式(即行为)。
结构图
这些图用于展示系统中元素的静态结构。它们描述系统的架构组织、系统的物理元素、系统的运行时刻配置和业务中领域相关的元素等。UML 结构图包括:
- 包图
- 类图
- 组件图
- 部署图
- 对象图
- 组合结构图
行为图
在所有的软件系统中,事件都是动态发生的:对象被创建和销毁,对象之间按次序发出消息,而且在某些系统中,外部的事件会触发某些对象上的操作。利用下面这些图来描述问题的动态行为语义或它的实现:
- 用例图
- 活动图
- 状态图
- 交互图
- 序列图
- 通信图
- 交互概述图
- 时间图
下面将详细介绍三种可以在掘金编辑器中使用的 UML 图,类图、序列图、状态图。
掘金编辑器中 UML 图是使用 mermaid 实现的,mermaid 是一个非常棒的制图工具,你可以在它的官网了解更多。
类图(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
- 面向对象分析与设计(第三版)格雷迪·布奇