【程式設計師小知識】使用 PlantUML 畫 UML 類圖
theme: smartblue highlight: isbl-editor-dark
小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
大家平日在寫技術文件時,往往都有畫 UML 圖的需要,很多人使用 PrecessOn 或者 darw.io 等來繪製 UML ,勉強可用但是不夠專業。這裡為大家推薦一個專門畫UML的工具: PlantUML
1. PlantUML
PlantUML 誕生於 2009 年,知道的人多但是使用的人少。因為它使用特殊的 DSL 進行畫圖,相較與其他工具,PlantUML 的圖不是“畫”出來的而是“寫”出來的。
雖然有一定學習成本,但是卻可以畫出更專業的UML圖,而且文字格式也便於儲存。本文總結 PlantUML 的基本用法,幫助大家快速入門。
安裝環境
PlantUML 是一個 java 程式,所以有 JDK 就能跑。可以從官網直接下載 jar 檔案執行,當然它也提供了 IDEA 和 VSCode 的外掛。
需要注意的是,PlantUML 的本地渲染依賴 Graphviz ,需要提前安裝並配置環境變數。如果你使用 VSCode 外掛,也可以藉助雲端渲染,只需要作如下配置:
之後就可以在 VSCode 中一邊“寫” UML 一邊預覽了。
接下來我們學習如何寫出漂亮的 UML ,像學習其他語言一樣 從 Hello World
開始。
2. Hello World
//hello.pu
@startuml
Hello <|-- World
@enduml
PlantUML 檔案通常以 .pu
為字尾,
命令列指定 pu
,
cmd
java -jar plantuml.jar hello.pu
在當前目錄下會將 @startuml
與 @enduml
之間的部分生成生成同名的 png
除了.pu
以外,各種原始碼檔案中的 @startuml
與 @enduml
也能識別並生成 png,
例如 .c
,.c++
, .htm
, .java
等等,因此我們可以將原始碼配套的 UML 寫入到註釋中:
java
/**
* @startuml
* class JavaSource {
* - String message
* }
* @enduml
*/
public class JavaSource {}
需要注意,一組@startuml/@enduml
對應一張 png,如果一個檔案中有多組,則生成的 png 檔名會新增自增數字字尾。 此外也可以緊跟在 @startuml
之後指定檔名
```cmd
@startuml foo
class Foo
@enduml
@startuml bar class Bar @enduml
@startuml baz class Baz @enduml ```
3. 基本語法
註釋
單引號後面跟隨的內容是註釋
cmd
@startuml no-scale
' 這是註釋
Hello <|-- World
@enduml
Title
title
後跟標題
uml
@startuml
title Hello Title
Hello <|-- World
@enduml
多行 title
title
與 end title
之間的輸入可以換行
cmd
@startuml
title
Hello
Title
end title
Hello <|-- World
@enduml
標題樣式
PlantUML 支援使用 Creole 這種標記語言,來豐富文字樣式,Creole 的用法類似 markdown。
參考 :https://en.wikipedia.org/wiki/Creole_%28markup%29
cmd
@startuml
title
* __Hello__
* **World**
end title
Hello <|-- World
@enduml
圖注
caption
之後跟的內容顯示為圖注
uml
@startuml
caption 圖1
Hello <|-- World
@enduml
header/footer
uml
@startuml
header Hello
Hello <|-- World
footer World
@enduml
header
footer
可以在頭部和尾部追加註釋。 預設 header
右對齊, footer
居中對齊。
對齊方式
cmd
@startuml
left header Hello
Hello <|-- World
right footer World
@enduml
在 header
footer
前加 left
center
right
可以設定對齊方式
多行 header/footer
跟 title
一樣, header ... end header
, footer ... end footer
```cmd @startuml header Hello Header end header
Hello <|-- World
footer World Footer end footer @enduml ```
放大率
```cmd @startuml no-scale Hello <|-- World @enduml
@startuml scale-1.5 scale 1.5 Hello <|-- World @enduml
@startuml scale-0.5 scale 0.5 Hello <|-- World @enduml ```
scale
可以為UML設定防大率
4. 類圖
Class
cmd
@startuml
class Hello
class World
@enduml
class
指定類
Interface
cmd
@startuml
interface Hello
interface World
@enduml
interface
指定介面
抽象類
cmd
@startuml
abstract class Hello
@enduml
abstract class
指定抽象類
列舉
cmd
@startuml
enum HelloWorld {
ONE
TWO
THREE
}
@enduml
enum
指定列舉, { ... }
定義列舉值
型別關係
UML中型別之間有六大關係: - 泛化(Generalization) - 實現(Realization) - 關聯(Association) - 聚合(Aggregation) - 組合(Composition) - 依賴(Dependency)
接下來逐一說明:
泛化
泛化關係就是類的繼承,java 中對應 extends
關鍵字。
cmd
@startuml
Child --|> Parent
Parent2 <|-- Child2
@enduml
<|--
--|>
指定繼承關係
實現
實現關係,對應 implements
關鍵字
cmd
@startuml
Plane ..|> Flyable
Flyable <|.. Plane
@enduml
..|>
, <|..
, 圓點表示虛線
依賴
依賴表示使用關係,java中, 被依賴的物件/類, 以方法引數, 區域性變數和靜態方法呼叫的形式出現。比如, 廚師在烹飪的時候看了一眼菜譜, 廚師"使用"了菜譜, 照著它炒完菜後,這種使用關係就結束了(臨時性).
cmd
@startuml
Chef ..> Recipe
@enduml
關聯
關聯關係,表示"擁有"。 相比依賴關係的臨時性和單向性,關聯關係具有長期性、平等性(可雙向),所以關聯表示的關係比依賴更強。比如現實生活中的夫妻, 師生等關係。長期存在並且是相互的關係。 此外關聯可以表示一對一,一對多,多對一,多對多等各種關係。
cmd
@startuml
Address <-- Husband
Husband <--> Wife
Husband2 -- Wife2
@enduml
因為比依賴關係更強, 所以是實線+箭頭。 雙向關聯可以省略箭頭。
後面兩種關係 "聚合" 和 "組合",都屬於關聯關係, 用來表示關聯關係中整體與部分的關係。java 中 一個 Class 與其成員變數 Class 型別之間就是這種整體與部分的關聯關係。
聚合
聚合關係相對於組合弱一些,整體與部分是可分離的。 比如部門與員工,部門有許多員工,員工離職了部門仍然存在,不受影響。反之部門解散了,員工可以去其他部門(整體與部分可分離)
cmd
@startuml
Department o-- Employee
@enduml
o
表示空心菱形
組合
組合關係中,整體與部分是不可分離的,整體與部分的生命週期保持一致,少了對方自己的存在無意義。例如人體是有四肢組成的,四肢不能脫離人體存在,人體少了四肢也難言完整
cmd
@startuml
Body "1" *-- "2" Arm
Body "1" *-- "2" Leg
@enduml
*
表示實心菱形
同時也看到了一對多時的數字表示方法,雙引號"
包裹,放線上段與Class之間。 多對多也同樣。
最後再總結一下六大關係
||繼承 |實現 |依賴 |關聯| 聚合 |組合| |--|--|--|--|--|--|--| |關係含義 |功能擴充套件| 功能實現| 使用| 擁有| 整體-部分(has-a)| 整體-部分(contains-a)| |關係特性| -| -| 臨時性,單向性 |長期性,可雙向(平等性)| 整體與部分可分離 |整體與部分不可分離,生命週期一致| |java語法 |extends| implements |方法引數,區域性變數,靜態方法呼叫| 成員變數 |成員變數 |成員變數| |關係強弱 |強 |強 |弱| 較強 |較強 |非常強| |現實事例| 父子 |飛機/鳥可以飛 |廚師使用菜譜| 夫妻,師生| 部門-員工| 人體-四肢| |圖形指向 |箭頭指向父類 |箭頭指向介面 |箭頭指向被使用者 |指向被擁有者,可雙向 | 箭頭指向部分, 菱形指向整體| 箭頭指向部分,菱形指向整體|
綜合運用
```uml @startuml interface One interface Two interface Three extends Two interface Four class Five implements One, Three class Six extends Five implements Four { field: String method(): void } @enduml
```
成員變數、成員方法
uml
@startuml
class Hello {
one: String
three(param1: String, param2: int): boolean
String two
int four(List<String> param)
}
@enduml
class
定義後跟大括號,宣告成員,然後按照 變數名:型別
的順序宣告,型別後置。方法和成員的順序上可以混在一起,最終成圖是,會自動分為兩組
成員可見性
UML 使用以下符號表示可見性
| Character | Visibility |
| --------- | ------------------- |
| -
| private
|
| #
| protected
|
| ~
| package private
|
| +
| public
|
但是 PlantUML 將這種文字符合進一步圖形化: ```cmd @startuml class Hello { - privateField: int # protectedField: int ~ packagePrivateField: int + publicField: int
- privateMethod(): void
# protectedMethod(): void
~ packagePrivateMethod(): void
+ publicMethod(): void
} @enduml ```
當然,也可以關閉這種圖形化符合,繼續使用文字元號
```cmd @startuml skinparam classAttributeIconSize 0 class Hello { - privateField: int # protectedField: int ~ packagePrivateField: int + publicField: int
- privateMethod(): void
# protectedMethod(): void
~ packagePrivateMethod(): void
+ publicMethod(): void
} @enduml ```
通過 skinparam classAttributeIconSize 0
關閉圖形化符號
抽象方法
cmd
@startuml
class Hello {
{abstract} one: int
{abstract} two(): int
}
@enduml
成員前面加 {abstract}
標記位抽象成員
靜態方法
cmd
@startuml
class Hello {
{static} ONE: int
{static} two(): int
}
@enduml
新增 {static}
表示靜態方法
泛型
cmd
@startuml
class Hello<H>
class World<W>
@enduml
類名後跟<泛型>
包圖
```cmd @startuml package one.two { class Hello }
package three.four { World -- Hello } @enduml ```
package <name> {...}
中可以寫類 UML 圖
包圖中的宣告順序
```cmd @startuml package three.four { World -- Hello }
package one.two { class Hello } @enduml ```
包圖的順序很重要,如上圖 one.two
中的類被 three.four
依賴,所以應該寫到先面, 以為 Hello
會宣告在第一個出現的包中。
備註(note)
```cmd @startuml class Fizz note left: fizz
class Buzz note right: buzz
class Foo note top: foo
class Bar note bottom: bar @enduml ```
使用 note <top|bottom|left|right>: <備註>
為 UML 圖新增備註, 備註內容可以是 Creole 語法
指定目標類
cmd
@startuml
Fizz -- Buzz
note left of Fizz: fizz
note right of Buzz: buzz
@enduml
note <位置> of <目標>: <備註>
用來為指定目標 Class 生成備註
為類關係進行備註
cmd
@startuml
Fizz -- Buzz
note on link: fizz-buzz
note left: buzz
@enduml
note on link: <備註>
可以在類圖的關係中新增備註
給備註加名字
```cmd @startuml note "Hello World" as n1 Hello -- n1 World .. n1
note "Fizz Buzz" as n2 @enduml ```
note "<備註>" as <名字>
用來給備註設定名字,有了名字後,可以通過名字將一個備註關聯到多個Class
多行備註
```cmd @startuml class Hello note left Hello World end note
Fizz -- Buzz note on link Fizz Buzz end note note left of Fizz fizz buzz end note
note as n1 Foo Bar end note @enduml ```
end note
用來結束多行的備註
- Google I/O :Android Jetpack 最新變化(二) Performance
- Google I/O :Android Jetpack 最新變化(一) Architecture
- Google I/O :Android Jetpack 最新變化(四)Compose
- Google I/O :Android Jetpack 最新變化(三)UI
- 一文看懂 Jetpack Compose 快照系統
- 聊聊 Kotlin 代理的“缺陷”與應對
- AAB 扶正!APK 再見!
- 面試必備:Kotlin 執行緒同步的 N 種方法
- Jetpack MVVM 七宗罪之六:ViewModel 介面暴露不合理
- CreationExtras 來了,建立 ViewModel 的新方式
- Kotlin DSL 實戰:像 Compose 一樣寫程式碼
- 為什麼 RxJava 有 Single / Maybe 等單發資料型別,而 Flow 沒有?
- 使用整潔架構優化你的 Gradle Module
- 一道面試題:介紹一下 Fragment 間的通訊方式?
- 【程式碼吸貓】使用 Google MLKit 進行影象識別
- Kotlin 1.6 正式釋出,帶來哪些新特性?
- Android Dev Summit '21 精彩內容盤點
- @OnLifecycleEnvent 被廢棄,替代方案更簡單
- Jetpack Navigation 實現自定義 View 導航
- 實現一個 Coroutine 版 DialogFragment