uml
uml: unified module language,统一建模语言。它制定了一系列图表的样式,来描述软件的结构和流程。通过uml图,软件开发者可以方便、清晰地使用图表来描述软件的设计,方便彼此交流。
uml中常用的图表:
- 类图: 描述类之间的结构
- 组件图: 描述系统范围哪些组件,例如订单模块、支付模块,以及组件之间的调用关系
- 活动图: 类似流程图,展示复杂的业务流程
- 时序图: 组件之间按照时序的调用过程
- 用例图: 展示系统有哪些功能,以及角色的操作流程
plantUML简介
plantUML: 一个开源工具,能够让用户用文本的方式来绘制uml图表,方便图表的制作,同时,文本还可以接入版本管理系统,方便管理。
安装: 我是在vscode中安装了plantUML相关的插件
plantUML文件的后缀名是 puml
基本语法
图表的开始和结束: @startuml、@enduml,图表的开始标记和结束标记,所有的内容都要在这个标记内
图表中的注释: ' ,单引号后面的内容就是注释,单引号必须位于一行的开头
绘制图表
类图
用于展示类的静态结构,包括类的属性、方法、类与类之间的关联关系
基础语法
声明一个类:
text
class 类名 {
- 属性: 类型
+ 方法(参数: 类型): 返回值
{static} + 静态方法(参数: 类型): 返回值
}
访问修饰符:
+: public-: private#: protected~: 包访问权限
类和类之间的关系:
- 继承/泛化 : --|> A是B的子类
- 实现: ...|>,A实现B
- 聚合: o--,A包含B,但B可以独立存在
- 组合: *--,A包含B,B不能独立于A存在
- 关联: -->,A知道B,例如成员变量引用
- 依赖: ...>,A使用B,例如方法参数、局部变量
- 双向关联: <-->,双方互相直到,互有成员引用
多重性: 一个类的实例可以与另一个类的多少个实例建立关系, 例如 User "1" --> "0...*" Order : 一个用户拥有零个或多个订单
- 1 : 恰好一个
- 0...* : 零个或多个
- n...m : n到m个
案例: 属性和属性分组的类图
案例: 属性和属性分组是两个独立的实体,它们之间有一张关联表,画出它们的类图
java
// 属性
public class Attr {
private Long id;
// 属性名
private String name;
// 排序字段
private Integer sort;
}
// 属性分组
public class AttrGroup {
private Long id;
// 分组名
private String name;
// 排序字段
private Integer sort;
}
// 属性和属性分组的关联表
public class AttrAndAttrGroupRelation {
private Long id;
// 属性分组id
private Long attrGroupId;
// 属性id的集合
private Long attrId;
}
类图:
puml
@startuml 属性和属性分组类图
' left to right direction 从左到右布局
title 属性和属性分组类图
' 实体类
' 布局设计,属性和属性值放到一起
together {
class Attr {
- id: Long
- name: String // 属性名
- sort: Integer // 排序字段
}
class AttrGroup {
- id: Long
- name: String // 属性分组名
- sort: Integer // 排序字段
}
}
class AttrAndAttrGroupRelation {
- id: Long
- attrGroupId: Long // 属性分组id
- attrId: Long // 属性id
}
' 实体类之间的关系
Attr "1" --> "1" AttrAndAttrGroupRelation : 一个属性只可以\n关联一个属性分组
AttrGroup "1" --> "0..*" AttrAndAttrGroupRelation : 一个属性分组下\n有零个或多个属性
AttrAndAttrGroupRelation --> Attr : 引用 (attrId)
AttrAndAttrGroupRelation ..> AttrGroup : 引用 (attrGroupId)
' ========== 注释 ==========
note top of Attr
属性
end note
note top of AttrGroup
属性分组
end note
@enduml
组件图
展示系统的物理结构,即系统由哪些组件组成、组件之间的依赖关系
基本语法
组件图中的组件:
- 包: 逻辑分层
- 组件: 可独立部署的模块
- 接口: 组件提供的服务
- 端口: 组件对外交互的出入口
组件之间的关系:
- 关联: -->,强关系,A 知道 B 的存在,B 是 A 的一部分或长期依赖
- 依赖: ...>,弱关系,A 临时使用 B,用完即走
- 实现: ...|>,组件实现了某个接口
声明组件名称: [组件名称] as 别名
案例: 系统部署组件图
puml
@startuml 系统部署组件图(优化)
title 系统部署组件图
' ========== 用户(外部) ==========
actor "用户" as User
' ========== Web服务器层 ==========
package "Web服务器(Nginx)" #LightBlue {
[静态资源] as static
[反向代理] as proxy
[负载均衡] as LB
}
' ========== 应用服务器层 ==========
package "应用服务器(Tomcat)" #LightGreen {
[Spring Boot] as App1
[Spring Boot] as App2
}
' ========== 数据库服务器层 ==========
package "数据库服务器" #LightCoral {
[MySQL] as DB1
[MySQL] as DB2
[Redis] as Cache
}
' ========== 消息服务器层 ==========
package "消息服务器" #LightPink {
[RabbitMQ] as MQ
}
' ========== 连接关系 ==========
' 用户通过域名访问 Web 服务器(负载均衡器)
User --> LB : 访问 Web 服务器
' Web 服务器内部处理
LB --> App1 : 负载均衡转发
LB --> App2 : 负载均衡转发
' 应用服务器访问数据库
App1 --> DB1 : 读写数据
App2 --> DB2 : 读写数据
App1 --> Cache : 缓存读写
App2 --> Cache : 缓存读写
' 应用服务器发送消息
App1 --> MQ : 发送消息
App2 --> MQ : 发送消息
@enduml
活动图(流程图)
语法
开始/结束: start、end,必须成对出现
普通操作: : 操作内容 ; , 操作内容以冒号开头,以分号结尾
条件判断:
text
if (条件) then (是)
: 操作内容;
else (否)
: 操作内容2;
endif
循环:
text
while(条件) is(是)
:操作内容;
endwhile
并行处理: fork 、 fork again 、 end fork
组件: 表示以下操作都是在该组件中完成的
text
partition 组件名称 {
: 操作;
}
节点的注释:
text
note right // right表示注释出现在右边,注释内容注意换行,一行不要太长
注释内容
end note
退出流程: stop
案例 快速排序的流程图
这里先简单介绍一下快排的流程,尝试根据下面的流程,画出流程图。
快排的流程:
- 选定一个数作为基准数,通常是数组最左边的数
- 然后再初始化两个指针,指向数组的开头和结尾
- 一趟交换:
- 结尾的指针先向前寻找,找比基准数小的元素,
- 然后开头指针向后寻找,找比基准数大的元素,
- 如果指针没有相遇,交换这两个指针处的值,
- 然后继续寻找,直到指针相遇,
- 然后交换指针处的值和基准数,(因为是结尾的指针先向前寻找,所以此时指针指向的元素一定小于基准数,或者后面的元素都比基准数大,指针指向基准数本身。一趟寻找完成以后,数组被分为两部分)
- 继续寻找直到左右指针相遇
- 然后继续对这两部分递归地进行快排,直到每一部分都为1,此时,整个数组有序。
流程图如下:
puml
@startuml 快速排序
title 快速排序的流程图
start
: 选定一个数作为基准数 base, 通常是数组中的第一个元素;
: 初始化两个指针 左指针 l、 右指针 r 指向数组的开头和结尾 ;
if (左指针 == 右指针) then (是)
: 退出排序;
' 退出程序
stop
else (否)
endif
partition 一趟交换 {
while (左指针 < 右指针) is (进入循环)
: 右指针向前寻找;
while (循环查找: 左指针< 右指针 && 右指针处的元素 > 基准数) is (是)
: 右指针向前移动;
endwhile(否)
: 左指针向后寻找 ;
while (循环查找: 左指针 < 右指针 && 左指针处的元素 < 基准数) is (是)
: 左指针向后移动;
endwhile(否)
if (左指针 != 右指针) then (是)
: 交换左右指针处的值;
: 左指针后移;
: 右指针前移;
endif
endwhile(左右指针重合,退出循环)
partition 基准数和右指针处的值交换 {
: 右指针处的值赋值给数组开头;
: 基准数(base)赋值给右指针处的值;
note right
因为是右指针先向前寻找,所以当左右
指针重合后,指针指向的元素一定小于
基准数,或者后面的元素都比基准数大,
指针指向基准数本身。交换基准数和
右指针处的值,因为基准数是数组开头的
元素,所以先把右指针处的值赋值给数组
开头,再把基准数的值赋值给右指针处
end note
}
}
: 一趟交换之后,数组从右指针处被分为两部分;
: 递归地对左边进行快排;
note right
左边是从数组开头到右指针减1,
它们都比基准数小
end note
: 递归地对右边进行快排;
note right
右边是右指针加1到数组结尾,
它们都比基准数大
end note
end
@enduml
案例2 多分支流程
需求: 用户在界面上的操作按钮,有按钮1、按钮2、按钮3,每个按钮都会执行不同的操作,我想要画一个流程图,描述用户在点击不同的按钮后,进行的不同操作
流程图:
puml
@startuml 并行操作的示例
title 并行操作的示例
start
: 用户进入页面;
: 页面弹出三个按钮;
fork
: 用户点击按钮1;
: 按钮1返回hello world;
fork again
: 用户点击按钮2;
: 按钮2返回java;
fork again
: 用户点击按钮3;
: 按钮2返回paython;
end fork
: 流程结束;
end
@enduml
在快排的案例中,没有展示出并行流程,例如,流程1、流程2、流程3是独立的,这里补充一个案例,描述这种流程。
时序图
基础语法
声明参与者的关键字:
- participant : 普通参与者,矩形
- actor : 角色用户,小人
- boundary : 边界
- entity : 实体
- database : 数据库
声明参与者的语法:
text
actor "用户" as User // 随后使用User来代表时序图中的一个组件
对消息编号: autonumer
消息类型:
- -> : 实线实心箭头,代表同步请求
- --> : 虚线实心箭头,代表异步请求
激活、销毁对象: activate、deactivate,表示参与者在特定时间段的激活状态
text
activate 角色
A -> B: A调用B
deactivate 角色
alt分支: if else
text
client -> server : 提交支付
alt 余额充足
server -> client : 支付成功
else 余额不足
server -> client : 支付失败
end
opt: 可选分支,有if无else
text
opt 开启短信通知
server -> sms : 发送短信
end
loop: 循环
text
loop 重试3次
client -> server : 上传文件
par: 并行分支
text
par 分支1: 写数据库
server -> db : 写数据库
par 分支2: 写缓存
server -> cache : 写缓存
end
案例 登录流程
用户登录的流程: 用户进入登录页面,输入账号、密码进行登录,最多允许重试三次,登录成功后,后端生成jwt token,返回给前端
流程图:
puml
@startuml 用户登录流程
title 用户登录流程
' 声明参与者
actor "用户" as User
participant "前端页面" as Frontend
participant "登录服务" as LoginService
database "数据库" as DB
database "缓存" as Cache
' 开始交互
User -> Frontend : 进入登录页面
User -> Frontend : 输入 用户名 + 密码
activate Frontend
Frontend -> Frontend : 校验用户输入数据的格式
alt 校验不通过
Frontend -> User : 输入不正确
else 校验通过
Frontend -> LoginService : PSOT /login , 用户名 + 密码,\n密码使用算法加密
LoginService -> Cache : 查询是否被锁定
activate Cache
Cache -> LoginService : 返回锁定状态
deactivate Cache
alt 账号被锁定
LoginService -> Frontend : 返回"账号被锁定"
Frontend -> User : 向用户展示错误信息
else 未锁定
LoginService -> DB : 根据用户名查询用户信息
activate DB
DB -> LoginService : 返回用户信息
deactivate DB
LoginService -> LoginService : 校验密码是否一致
alt 密码错误
LoginService -> Cache : 失败次数加1
activate Cache
Cache -> LoginService : 返回当前的失败次数
deactivate Cache
alt 失败次数 >= 3次
LoginService -> Frontend : 错误次数超过3次,当前账号已被锁定
Frontend -> User : 展示错误信息,账号被锁定
else 失败次数 < 3次
LoginService -> Frontend : 返回账号密码错误
Frontend -> User : 展示错误信息,账号密码错误
end
else 密码正确
LoginService -> Cache : 清除失败次数
activate Cache
Cache -> LoginService : 返回清除结果
deactivate Cache
LoginService -> LoginService : 生成 jwt token
LoginService -> Cache : 把token保存到缓存中
activate Cache
Cache -> LoginService : 返回保存结果
deactivate Cache
LoginService -> Frontend : 返回登录成功、token
Frontend -> User : 跳转到系统首页
User -> Frontend : 用户在登录态操作页面,\n每次请求都会携带token,\n后端根据token从缓存中获取用户信息
end
end
end
deactivate Frontend
@enduml
用例图
展示系统对外提供的基本功能和可以操作这些功能的不同角色,还可以展示不同功能之间的关联关系。
基础语法
定义参与者: actor, actor "用户名称" as 别名,表示使用系统的人,用户名称在图片上展示,别名在文件内部使用
声明用例: usecase, usecase "用例名称" as 别名 ,一个用例就是系统的一个功能
系统边界: package
text
package 订单管理系统 {
usecase "创建订单" as UC1
usecase "取消订单" as UC2
}
注释: note left/right of 元素
关联关系: --> , 表示角色和用例之间的调用关系,用户别名 --> 用例别名,角色可以调用这个功能
用例之间的关系:
- 一个用例包含另一个用例: include, ...> ,
UC4(下单) ..> UC1(登录) : <<include>>,执行下单时,必须先登录 - 一个用例在特定条件下可以扩展另一个用例: extend, ...>,
UC5(支付) ..> UC16(使用优惠券) : <<extend>>,执行"支付"时,在特定条件下可以执行"使用优惠券" - 用例之间的父子关系:
text
actor "支付方式" as Payment
usecase "微信支付" as WX
usecase "支付宝支付" as Ali
WX --> Payment
Ali --> Payment
解释: 微信支付和支付宝支付都是支付方式的具体实现
案例: 图书管理系统
图书管理系统: 有两个角色,管理员和用户,用户可以查询、借阅图书,管理可以对图书进行增删改查
puml
@startuml 图书管理系统用例图
title 图书管理系统用例图
actor "读者" as reader
actor "管理员" as admin
package 图书管理系统 {
usecase "查询图书" as query_book
usecase "借阅图书" as borrow_book
usecase "归还图书" as return_book
usecase "续借图书" as continue_borrow_book
usecase "管理图书" as admin_book
usecase "管理读者" as admin_reader
usecase "罚款管理" as admin_penalty
}
reader --> query_book
reader --> borrow_book
reader --> return_book
reader --> continue_borrow_book
admin --> admin_book
admin --> admin_reader
admin --> admin_penalty
borrow_book ..> query_book : <<include>>\n(先查询才能借阅)
return_book ..> borrow_book : <<include>>\n(先借阅才能归还)
continue_borrow_book ..> borrow_book : <<include>>\n(先借阅才能续借)
admin_penalty ..> return_book : <<extend>>\n超时归还触发罚款
@enduml
总结
uml提供的图表类型还有很多,plantUML支持的更多,这里只介绍最常见的几种