基于Mermaid的UML图像绘制
如何用Mermaid绘制各种各样的UML图像
如何绘制UML的类图、用例图和时序图?
UML图有很多种,根据二八定律,掌握其中20%的部分就能处理80%的工作了。这里重点说明UML类图、UML用例图、UML时序图以及甘特图的样式和用法。甘特图可以用来规划项目时间。
关于Mermaid绘制UML类图、用例图、时序图的教程,参看Mermaid Official Document。本文的使用的Mermaid版本为8.5.2。
refrence
- https://zhuanlan.zhihu.com/p/109655171
UML类图
类图是面向对象系统建模中最常用和最重要的图,是定义其它图的基础。类图主要是用来显示系统中的类、接口以及它们之间的静态结构和关系的一种静态模型。类图中最基本的元素是类、接口。软件设计师设计出类图后,程序员就可以用代码实现类图中包含的内容。
- UML具体类由矩形框表示,矩形框分为三层:第一层是类名字。第二层是类的成员变量;第三层是类的方法。
- 成员变量以及方法前的访问权限修饰符(可见性)用符号来表示:
- “+”表示
public
; - “-”表示
private
; - “#”表示
protected
; - 不带符号表示
default
; - “~” 表示
Package/Internal
。
- “+”表示
- 类的修饰符
<<Interface>>
To represent an Interface class 接口<<abstract>>
To represent an abstract class 抽象类<<Service>>
To represent a service class
<<enumeration>>
To represent an enum 枚举类
具体类
具体类是Java中最常见的类
Java Code
1 |
|
UML 类图
classDiagram
Animal <|-- Duck
Animal <|-- Zebra
class Animal{
+int age
-String gendar
#String skill
+isMammal()
-mate()
}
class Duck {
+beakColor String
+swim()
+quack()
}
class Zebra {
+run()
+is_wild()
}
源码
1 |
|
抽象类
抽象类在UML类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用斜体字表示,如下所示。
抽象类
Java code
1 |
|
UML类图
classDiagram
class Employee {
-String address
-String name
+computePay()
+mainCheck()
}
<<abstract>> Employee %% 声明放到后面
源码 1
2
3
4
5
6
7
8
9classDiagram
class Employee {
-String address
-String name
+computePay()
+mainCheck()
} %% 这里实际上应该是 +computePay() double 代表这是一个抽象类
<<abstract>> Employee %% 声明放到后面
静态类
static作为静态成员变量和成员函数的修饰符,意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见。静态有一些特点:
- 全局唯一,任何一次的修改都是全局性的影响
- 只加载一次,优先于非静态
- 使用方式上不依赖于实例对象。
- 生命周期属于类级别,从JVM 加载开始到JVM卸载结束。
静态类只能是内部类,如果在外部类声明为static,程序会编译都不会过。
Java Code
1 |
|
UML类图
classDiagram
class Builder {
-String name
-int age
+Builder(age)
+withName(name)
+withAge(age)
+build(b)
}
<<static>> Builder %%声明放到最后面
源码
1 |
|
接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。接口不能包含成员变量,除了 static 和 final 变量。接口不是被类继承了,而是要被类实现。接口支持多种实现。
Java
1 |
|
UML 类图
classDiagram
class Sports {
+int type
-int number
+setHomeTeam(name)
+setVisitingTeam(name)
}
<<interface>> Sports
源码
1 |
|
枚举类
枚举是实现单例的最简便的方式。
Java Code
用枚举类进行定义
1 |
|
用类class实现相同的定义
1 |
|
UML 类图
classDiagram
class Season1 {
+Season2 SPRING
+Season2 SUMMER
+Season2 AUTUMN
+Season2 WINTER
String name
Season1()
Season1(name)
}
<<enumeration>> Season1
源码
1 |
|
包
Java Code
1 |
|
UML
mermaid并不支持UML类图中的包
关系
总结
类和类、类和接口、接口和接口之间存在一定关系,UML类图中一般会有连线指明它们之间的关系。关系共有六种类型,分别是
实现关系(Realization)
继承/泛化关系(Inheritance)
关联关系(Association)
- 依赖关系(Dependency)
- 组合关系(Composition)
- 聚合关系(Aggregation)
单纯的链接关系也可以分为实链接和虚链接
- 链接关系(Link(Solid)/Link(Dashed))
graph LR
关系 --> 接口和类之间 --> 实现关系
关系 --> 对象与对象之间
对象与对象之间 --> A[继承/泛化关系 is a]
对象与对象之间 --> B[关联关系]
B --> c[聚合关系 has a]
B --> D[组合关系 contains a]
B --> E[依赖关系 use a]
源码 1
2
3
4
5
6
7
8graph LR
关系 --> 接口和类之间 --> 实现关系
关系 --> 对象与对象之间
对象与对象之间 --> A[继承/泛化关系 is a]
对象与对象之间 --> B[关联关系]
B --> c[聚合关系 has a]
B --> D[组合关系 contains a]
B --> E[依赖关系 use a]
UML
classDiagram
classM ..|> classN : 实现(虚线)
classA --|> classB : 继承(实线)
classG --> classH : 关联(实线)
classE o--> classF : 聚合
源码 1
2
3
4
5classDiagram
classM ..|> classN : 实现(虚线)
classA --|> classB : 继承(实线)
classG --> classH : 关联(实线)
classE o--> classF : 聚合
classDiagram
classC *--> classD : 组合
classK ..> classL : 依赖(虚线)
classI -- classJ : 实链接(实线)
classO .. classP : 弱链接(虚线)
%% 实链接和弱链接不属于
源码
1 |
|
关系注释可以通过 classO .. classP : 弱链接(虚线)
这样的形式来实现。
实现关系
实现关系是指接口及其实现类之间的关系
1 |
|
实现关系的UML
classDiagram
class Runnable {
}
<<interface>> Runnable
class MyThread{
}
MyThread ..|> Runnable : 实现关系
源码
1 |
|
泛化/继承关系
泛化关系(Generalization)是指对象与对象之间的继承关系。如果对象A和对象B之间的“is a”关系成立,那么二者之间就存在继承关系,对象B是父对象,对象A是子对象。例如,一个教授“is a”老师,很显然教授 Professor
对象和员工 Teacher
对象之间存在继承关系,Teacher
对象是父对象,Professor
对象是子对象。
1 |
|
UML
classDiagram
class Teacher {
}
class Professor{
}
Teacher --|> Professor : 继承关
源码
1 |
|
关联关系
关联关系(Association)是指对象和对象之间的连接,它使一个对象知道另一个对象的属性和方法。在Java中,关联关系的代码表现形式为一个对象含有另一个对象的引用。也就是说,如果一个对象的类代码中,包含有另一个对象的引用,那么这两个对象之间就是关联关系。
关联关系有单向关联和双向关联。如果两个对象都知道(即可以调用)对方的公共属性和操作,那么二者就是双向关联。如果只有一个对象知道(即可以调用)另一个对象的公共属性和操作,那么就是单向关联。大多数关联都是单向关联,单向关联关系更容易建立和维护,有助于寻找可重用的类。
在UML图中,双向关联关系用带双箭头的实线或者无箭头的实线双线表示。单向关联用一个带箭头的实线表示,箭头指向被关联的对象。
1 |
|
UML
classDiagram
class Employee {
}
class TimeCard {
}
Employee --> TimeCard : 0...*
源码
1 |
|
一个对象可以持有其它对象的数组或者集合。在UML中,通过放置多重性(multipicity)表达式在关联线的末端来表示。多重性表达式可以是一个数字、一段范围或者是它们的组合。多重性允许的表达式示例如下:
- 数字:精确的数量
*
或者0..*
:表示0到多个0..1
:表示0或者1个,在Java中经常用一个空引用来实现1..*
:表示1到多个
依赖关系
依赖(Dependency)关系是一种弱关联关系。如果对象A用到对象B,但是和B的关系不是太明显的时候,就可以把这种关系看作是依赖关系。如果对象A依赖于对象B,则 A “use a” B。比如驾驶员和汽车的关系,驾驶员使用汽车,二者之间就是依赖关系。
依赖关系在Java中的具体代码表现形式为B为A的构造器或方法中的局部变量、方法或构造器的参数、方法的返回值,或者A调用B的静态方法,满足以上条件,则A依赖B。
1 |
|
UML
classDiagram
class Driver {
}
class Car {
}
Driver ..> Car : 虚线
源码
1 |
|
代码清单1所示的B类定义了一个成员变量 field1,一个普通方法 method1() 和一个静态方法 method2()。
1 |
|
代码清单2所示的A类依赖于B类,在A类中定义了四个方法,分别演示四种依赖形式。
1 |
|
聚合关系
聚合(Aggregation)是关联关系的一种特例,它体现的是整体与部分的拥有关系,即 “has a” 的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享,所以聚合关系也常称为共享关系。例如,公司部门与员工的关系,一个员工可以属于多个部门,一个部门撤消了,员工可以转到其它部门。
聚合关系用空心菱形加实线箭头表示,空心菱形在整体(部门)一方,箭头指向部分(员工)一方。
1 |
|
UML
classDiagram
class Department
class Employee
Department o--> Employee
源码
1 |
|
聚合是可分开的,员工可以从部门分开。
组合是不可分的,轮胎不能从汽车中分开。
组合关系
组合(Composition)也是关联关系的一种特例,它同样体现整体与部分间的包含关系,即 “contains a” 的关系。但此时整体与部分是不可分的,部分也不能给其它整体共享,作为整体的对象负责部分的对象的生命周期。这种关系比聚合更强,也称为强聚合。如果A
组合B
,则A
需要知道B
的生存周期,即可能A
负责生成或者释放B
,或者A
通过某种途径知道B
的生成和释放。
例如,人包含头、躯干、四肢,它们的生命周期一致。当人出生时,头、躯干、四肢同时诞生。当人死亡时,作为人体组成部分的头、躯干、四肢同时死亡。
在UML图中,组合关系用实心菱形加实线箭头表示,实心菱形在整体(汽车)一方,箭头指向部分(轮胎)一方。
1 |
|
UML
classDiagram
class Car
class Tire
Car *--> Tire : 0...*
源码
1 |
|
在Java代码形式上,聚合和组合关系中的部分对象是整体对象的一个成员变量。但是,在实际应用开发时,两个对象之间的关系到底是聚合还是组合,有时候很难区别。在Java中,仅从类代码本身是区分不了聚合和组合的。如果一定要区分,那么如果在删除整体对象的时候,必须删掉部分对象,那么就是组合关系,否则可能就是聚合关系。从业务角度上来看,如果作为整体的对象必须要部分对象的参与,才能完成自己的职责,那么二者之间就是组合关系,否则就是聚合关系。
例如,汽车与轮胎,汽车作为整体,轮胎作为部分。如果用在二手车销售业务环境下,二者之间就是聚合关系。因为轮胎作为汽车的一个组成部分,它和汽车可以分别生产以后装配起来使用,但汽车可以换新轮胎,轮胎也可以卸下来给其它汽车使用。如果用在驾驶系统业务环境上,汽车如果没有轮胎,就无法完成行驶任务,二者之间就是一个组合关系。再比如网上书店业务中的订单和订单项之间的关系,如果订单没有订单项,也就无法完成订单的业务,所以二者之间是组合关系。而购物车和商品之间的关系,因为商品的生命周期并不被购物车控制,商品可以被多个购物车共享,因此,二者之间是聚合关系。
UML用例图
TODO
UML时序图
UML时序图的组成部分
参与者 / 对象
参与者(Actor) / 对象(Object)按照一定顺序从左到右排列。UML中并没有对排列顺序作出任何要求,但是经过长期的积累得出以下经验:
- 活动的起始点放在最左侧。
- 交互频繁的参与者或对象靠拢。
在UML中,参与者通常使用以下表示方法:
1 |
|
在UML中,对于参与者或对象,我们可以使用 as 来给对象起别名,方便后面的引用。 除了使用 actor 和 participant 声明参与者和对象外,还可以通过 boundary、control、entity 和 database 来声明,仅图标差异。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
title: 声明对象
participant te as 测试工程师
生命线
生命线(lifeline):对象表示一个具体的实例对象,时序图只表达其中一个实例对象的交互流程。(垂直虚线)
- 如果系统中有多个实例对象,每个实例对象流程有所区别,那么应该画多个时序图。
- 时序图表达的是系统某一时段,对象的交互过程。
- 这个对象不仅是一个 class 的实例
- 也有可能是一个子系统
- 分布式集群里的某个节点
下图总的Alice和John就是两条生命线.
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
激活期
激活期是指对象在整个活动中获得了焦点,需要一段时间来执行某个动作,在UML图中是一个空心的矩形表示。
下图中的John就存在一段时间的激活期(空心灰色的矩形)。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
Alice->>John: Hello John, how are you?
activate John
John-->>Alice: Great!
deactivate John
系统边界
系统边界(gate):表明消息从外部对象产生,最终返回给外部对象。
控制焦点
控制焦点(execution specification,informally called activation):简单的理解为调用了该对象的某个方法,该方法终止的标志为执行完相关的过程,完成方法调用,并且收到了相关的消息。
消息
消息(interaction message):只是表明了发送者向接收者进行了一次通讯,可能会代表一次 http 请求,也可能代表一个函数调用。对象之间的交互是通过相互发消息来实现的。一个对象通过发送消息请求另一个对象做事件。消息从源对象指向目标对象。消息一旦发送便将控制从源对象转移到目标对象。消息不仅可以从A对象发给B对象,还可以自己发给自己(下图中开发工程师发送信息给自己)。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant te as 测试工程师
participant de as 开发工程师
te->>de: There is a bug!
activate de
de ->> de: Fixing the Bug...
de-->>te: Wait a minute. I am try to fix it.
deactivate de
消息分为
mermaid中有以下几种箭头
Type | Description | Chinese | Usage |
---|---|---|---|
-> | Solid line without arrow | 无箭头实线 | 异步消息 |
--> | Dotted line without arrow | 无箭头虚线 | 无实义 |
->> | Solid line with arrowhead | 有箭头实线 | 同步消息 |
-->> | Dotted line with arrowhead | 有箭头虚线 | 返回消息 |
-x | Solid line with a cross at the end (async) | 带叉的有箭头实线 | 无实义 |
--x | Dotted line with a cross at the end (async) | 带叉的有箭头虚线 | 删除消息 |
同步消息
直到等待接收者返回消息,譬如 测试工程师 准备写测试用例,发了消息“给我需求文档”给产品经理,只有等待产品经理返回消息后,测试工程师才可以继续后续的操作。也可以视为函数调用:消息发送后,等待消息的回复。从代码的角度来说,就是执行了一个函数调用,并且等待函数的返回。(实线箭头 ->>
)
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant te as 测试工程师
participant pm as 产品经理
te ->> pm : 给我需求文档
异步消息
无需等待,发送消息后继续操作。譬如 测试工程师 拿到需求文档后,发消息“给我详细设计文档”给 开发工程师,此时测试工程师不需要等开发工程师给详设文档,在开发准备的过程中进行验收用例设计。发送了消息后不等待返回,立即处理后续的事情。从代码的角度来说,就是开了另一个线程执行函数调用不等待函数的返回结果。在UML中,使用空心箭头,mermaid中没有空心箭头, 所以选用直线表示 ->
。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant te as 测试工程师
participant de as 开发工程师
te -> de : 给我详细设计文档
返回消息
表示完成某个活动或动作后,给予消息源反馈内容。表明消息接受者已经处理完消息,把结果返回给发送者。从代码的角度来说,就是 A 调用了 B,B 返回了结果(无论是通过函数返回或函数回调的方式)。(虚线主干线条箭头)(虚线箭头 -->>
)
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant te as 测试工程师
participant de as 开发工程师
te ->> de: 出Bug了!
activate de
de ->> de: 改bug中
de -->> te: Bug已修复, 麻烦回归一下.
deactivate de
创建消息
即创建对象,A 给 B 发送了创建消息,从代码的角度来说就是在 A 的方法里,执行了 new B()
的操作。 (带注释实线箭头 ->>
)
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
activate Teacher
Teacher ->> Professor : Create
deactivate Teacher
删除消息
和创建对象相反,A 给 B 发送了删除的消息,从代码的角度来说就是在 A 的方法里,执行了 release B
的操作,在 C++等手动内存管理的场景会比较好举例,等价于释放了对象的内存。(带叉实线箭头加注释 --x
)
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
activate Teacher
Teacher --x Professor : Delete
deactivate Teacher
顺序图操作
添加标题
通过 title 关键字为顺序图设定名字。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
Title: Bug修复简易流程123
participant te as 测试工程师
participant de as 开发工程师
te ->> de: 出Bug了!
activate de
de ->> de: 改bug中
de -->> te: Bug已修复, 麻烦回归一下.
deactivate de
添加注释
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant John
participant Tom
Note left of John: Text in note
John ->> Tom: 提了个Bug
Note over Tom,John: A typical interaction
分割线
使用分割线可以把一个顺序图划分为不同的阶段。分割线使用 == 阶段名称 == 的形式来表示。Mermid似乎不支持分割线。
消息数字序号
当时序图中存在很多消息时,会产生很大的理解障碍。通过 autonumber 关键字,可以自动把消息按先后顺序加上数字序号。
完整mermaid代码
1 |
|
效果如下:
todo
添加底色
使用以下语法为区块添加底色
1 |
|
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
participant John
participant Tom
rect rgb(100, 255, 100, 0.1)
Note left of John: Text in note
John ->> Tom: 提了个Bug
Note over Tom,John: A typical interaction
end
分组
利用分组功能,可以把开发顺序分成不同的阶段。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
title:需求分析建议流程(不完全)
participant te as 测试工程师
participant pm as 产品经理
participant de as 开发工程师
par 需求文档获取
rect rgb(0, 255, 0, 0.1)
te ->> pm: 需求文档
activate pm
pm ->> pm: 写需求
pm -->> te: 返回需求
deactivate pm
end
and 需求分析
rect rgba(0, 0, 255, 0.1)
activate te
te ->> de: 详细设计文档
par 需求澄清
rect rgba(255, 0, 0, 0.1)
te ->> te: 了解需求
te ->> te: 需求反串讲解
end
end
end
Note over te,pm: 需求分析
Note over te,de: 测试设计与分析
deactivate te
end
条件判断
条件判断为顺序图提供了强大的逻辑判断能力,极大丰富了顺序图的使用范围。在UML中使用 alt
关键字来进行条件判断。alt
就是抉择的意思,用来指明在两个或更多的消息序列之间的互斥的选择,相当于经典的 if..else..
。抉择在任何场合下只发生一个序列。 可以在每个片段中设置一个临界来指示该片段可以运行的条件。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
title: 提交bug规范(不完全)
participant te as 测试工程师
participant dev as 开发工程师
te ->> dev: 提交bug
alt bug提交规范
rect rgb(0, 255, 0, 0.1)
dev ->> dev: 修改bug
dev -->> te: 修改完成,返回
end
else bug提交不规范
rect rgb(0, 100, 255, 0.1)
dev ->> te: 无效bug,信息不完整,请重新提交
end
end
循环
循环进行某一项活动,在UML中使用 loop 关键字对内部活动进行循环操作,直到条件满足后退出。类似 while
或者 for
。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
title:提交bug规范(不完全)
participant te as 测试工程师
participant tesys as 测试系统
te ->> te: 探索性测试
loop 直到测试完所有bug
rect rgb(255, 0, 0, 0.1)
te ->> tesys: 提交一个bug
tesys -->> te: 提交成功
end
end
超时
在两条消息之间添加延时,可以提高顺序图的阅读性。在UML中使用 … 来添加时延。
完整mermaid代码
1 |
|
实际效果
sequenceDiagram
title:回归测试
participant te as 测试工程师
participant dev as 开发工程师
te ->> dev: 提交bug
dev ->> dev: 修改bug
dev -->> te: 修改完成,提交测试
Note over te, dev: 回归测试(一天)
te ->> dev: 回归不通过, 重新修正bug
可以把 alt
和 loop
视为特殊的group分组
参与组分组
mermaid
没有实现参与者分组功能
box
mermaid
中没有 box
组件,但是语雀中有这个组件。
引用
引用(Interaction use):引用是表示在现有时序图里嵌入一个子流程,主要是用于简化时序图的表达。因为有些流程相对复杂,如果都画在一起,显得复杂,不好阅读。另一种场景就是复用流程。
甘特图
甘特图(Gantt chart)又称为横道图、条状图(Bar chart)。其通过条状图来显示项目,进度,和其他时间相关的系统进展的内在关系随着时间进展的情况。以提出者亨利·劳伦斯·甘特(Henry Laurence Gantt)先生的名字命名。
甘特图以图示通过活动列表和时间刻度表示出特定项目的顺序与持续时间。一条线条图,横轴表示时间,纵轴表示项目,线条表示期间计划和实际完成情况。直观表明计划何时进行,进展与要求的对比。便于管理者弄清项目的剩余任务,评估工作进度。
甘特图是以作业排序为目的,将活动与时间联系起来的最早尝试的工具之一,帮助企业描述工作中心、超时工作等资源的使用。甘特图包含以下三个含义:
- 以图形或表格的形式显示活动;
- 通用的显示进度的方法;
- 构造时含日历天和持续时间,不将周末节假算在进度内。 简单、醒目、便于编制,在管理中广泛应用。 甘特图按内容不同,分为计划图表、负荷图表、机器闲置图表、人员闲置图表和进度表五种形式
效果图
gantt
title 软件开发甘特图
dateFormat YYYY-MM-DD
section 设计
需求: done, des1, 2014-01-06,2014-01-08
原型: active,des2, 2014-01-09, 3d
UI设计: des3, after des2, 5d
未来任务: des4, after des3, 5d
section 开发
学习准备理解需求: crit, done, 2014-01-06,24h
设计框架: crit, done, after des2, 2d
开发: crit, active, 3d
未来任务: crit, 5d
休息: 2d
section 测试
功能测试: active, a1, after des3, 3d
压力测试: after a1, 20h
测试报告: 48h
源码 1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!