【程式設計師小知識】使用 PlantUML 畫 UML 類圖

語言: CN / TW / HK

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 為字尾,

命令列指定 pucmd 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

titleend 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 用來結束多行的備註