PlantUML デザインパターン
はじめに
PlantUMLは、テキストベースでUMLを記述するための、おそらく世界で最も普及したツール。
テキストベースなので、gitを使ったUMLの変更管理などが容易で、マウスを使わずにUMLをさくっと書けるメリットがある。
ここでは、PlantUMLを記述する上で参考になるデザインパターンを紹介する。
- パターン未適用
デザインパターン未適用の状態のPlantUMLファイルとその課題を述べる。 - パターン適用
デザインパターンを適用したPlantUMLファイルとその利点を述べる。
Skinparam分離パターン
パターン未適用
PlantUMLファイルのデザインの設定(sksinparam)を個別のクラス図ファイルの中に記載している。
ファイルが多くなるほど、共通的なデザインの設定を管理しにくくなる。
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
left to right direction
package shop {
class Shop
Shop *-- Item
}
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
package customer {
class Customer
Customer --> Item : buy
}
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
# 個別設定
left to right direction
package shop {
class Shop
Shop *-- Item
}
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
package customer {
class Customer
Customer --> Item : buy
}
クラスが増えるほどファイルが肥大化してメンテナンス性が劣化する。
パターン適用
共通設定を 専用ファイル skinparam.puml
に切り出し、図毎に個別に設定する個別設定だけを各ファイルの中で管理する。
以下の例では、3つの共通設定を skinparam.puml
に切り出し、1つの個別設定だけをshopクラスのファイルshop-class.puml
で管理する。
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
# 個別設定
left to right direction
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
left to right direction
package shop {
class Shop
Shop *-- Item
}
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
package customer {
class Customer
Customer --> Item : buy
}
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
# 共通設定
!include skinparam.puml
# 個別設定
left to right direction
package shop {
class Shop
Shop *-- Item
}
# 共通設定
!include skinparam.puml
package customer {
class Customer
Customer --> Item : buy
}
クラス増加時にもファイルが肥大化しにくく、メンテナンス性を維持できる。
Packageパターン
パターン未適用
1つのPlantUmlファイルに複数のパッケージが定義される。
package shop {
class Shop
Shop *-- Item
}
package customer {
class Customer
Customer --> Item : buy
}
package shop {
class Shop
Shop *-- Item
}
package customer {
class Customer
Customer --> Item : buy
}
クラスが増えるほどファイルが肥大化してメンテナンス性が劣化する。
パターン適用
パッケージ毎にファイルを分ける。
package shop {
class Shop
Shop *-- Item
}
package customer {
class Customer
Customer --> Item : buy
}
package shop {
class Shop
Shop *-- Item
}
package customer {
class Customer
Customer --> Item : buy
}
クラス増加時にもファイルが肥大化しにくく、メンテナンス性を維持できる。
Arrowパターン
パターン未適用
オブジェクト間の依存関係をextends
やimplements
といったキーワードで記述する。
class Parent
class Child extends Parent
class Parent
class Child extends Parent
この表現には以下の問題がある。
- 矢印向きを指定できない。
- 個々のオブジェクトを表現する
class
宣言と、"矢印"というレイアウトを指定するextends
キーワードが混ざっている。
パターン適用
Arrowパターンを適用した例を以下に示す。
class Parent
class Child
Parent <|-up- Child
class Parent
class Child
Parent <|-up- Child
Arrowパターンは個々のオブジェクトのコンテキストを保持するclass
宣言からレイアウト情報を保持する矢印を分離する。
コンテキストとレイアウトの分離により、より柔軟な図表現が可能となる。上記の例では矢印にup
キーワードを使うことで矢印の向きを逆向きにした。
Includeパターン
パターン未適用
start/endsub
と includesub
キーワードをつかって、PlantUMLファイルをオブジェクト指向の構造にまとめよう。
次のようなUMLを書くことを考えてみよう。
!startsub interface
package activities {
class Activity
}
!endsub
!startsub interface
abstract class ActivityEdge
!endsub
Activity "0..1" *-- "*" ActivityEdge
Activity
クラスはactivities
パッケージの一部であり、アブストラクトクラスActivityEdge
を集約する。このUMLを、1つのPlantUMLファイルに記述すると次のようになるだろう。
@startuml
package activities {
class Activity
}
abstract class ActivityEdge
Activity "0..1" *-- "*" ActivityEdge
@enduml
このとき、activities
パッケージ内のActivity
クラスとパッケージ外のActivityEdge
クラスが、同じファイル内に書かれることになる。
こうすると、たとえばactivitiesパッケージだけを図示したい場合や、他のクラス図にActivityクラスを載せたい場合にコードクローンするしかなく、不便である。
パターン適用例
ここで使えるのが、start/endsub
と includesub
キーワードである。前述のPlantUMLファイルをこのキーワードを使って2つのファイルに分割する。
@startuml
!startsub interface
package activities {
class Activity
}
!endsub
@enduml
@startuml
!includesub activity.puml!interface
!startsub interface
abstract class ActivityEdge
!endsub
Activity "0..1" *-- "*" ActivityEdge
@enduml
感嘆符!
は、PlantUMLのプリプロセッサ命令を意味する記号である。
activity.puml
の!startsub interface ... !endsub
は、interface
という名前のサブパートを宣言するプリプロセッサ命令である。このサブパートは、!includesub
キーワードを使って、別のPlantUmlから呼び出すことができる。
index.puml
の!includesub activity.puml!interface
は、activity.puml
のinterface
サブパートを呼び出すプリプロセッサ命令である。
このように、start/endsub
と includesub
キーワードを用いてactivities
パッケージを再利用しやすい別のファイルに分割することができた。
Compactパターン
パターン未適用
フィールドやメソッドを持たないクラスまで、フィールドとメソッドのセクションが表示されて見にくい。
たとえば、フィールドを持たないClerkクラスと、メソッドを持たないItemクラスがあったとする。
class Clerk {
+ sell()
}
class Item {
- price
}
class Clerk {
+ sell()
}
class Item {
- price
}
PlantUML は、デフォルトではフィールド、メソッドを持たない場合もセクションを表示してしまう。
クラスが1つ、2つの場合はまだいいが、クラスが増えてきたらどうだろうか。
package headquarters {
class Headquarter
Headquarter *-- CEO : belong
Headquarter *-- CTO : belong
Headquarter *-- CFO : belong
}
package shop {
class Shop
class Clerk {
+ sell()
}
class Item {
- price
}
Shop *-- Item : have
Shop *-- Clerk : belong
Order *-- Item : include
}
package customer {
class Customer {
- wallet
+ buy()
}
}
Headquarter *-- Shop : belong
Customer --> Item : buy
package headquarters {
class Headquarter
Headquarter *-- CEO : belong
Headquarter *-- CTO : belong
Headquarter *-- CFO : belong
}
package shop {
class Shop
class Clerk {
+ sell()
}
class Item {
- price
}
Shop *-- Item : have
Shop *-- Clerk : belong
Order *-- Item : include
}
package customer {
class Customer {
- wallet
+ buy()
}
}
Headquarter *-- Shop : belong
Customer --> Item : buy
クラスが増えるほど見にくくなるだろう。
パターン適用
"hide empty"を使って、空のフィールドとメソッドを非表示にする。
ファイルの冒頭に次の2行を追加する。
hide empty fields
hide empty methods
1行目は、空のフィールドを持つセクションを非表示にするスキンパラメータである。
2行目は、空のメソッドを持つセクションを非表示にするスキンパラメータである。
適用例を以下に示す。
hide empty fields
hide empty methods
package headquarters {
class Headquarter
Headquarter *-- CEO : belong
Headquarter *-- CTO : belong
Headquarter *-- CFO : belong
}
package shop {
class Shop
class Clerk {
+ sell()
}
class Item {
- price
}
Shop *-- Item : have
Shop *-- Clerk : belong
Order *-- Item : include
}
package customer {
class Customer {
- wallet
+ buy()
}
}
Headquarter *-- Shop : belong
Customer --> Item : buy
hide empty fields
hide empty methods
package headquarters {
class Headquarter
Headquarter *-- CEO : belong
Headquarter *-- CTO : belong
Headquarter *-- CFO : belong
}
package shop {
class Shop
class Clerk {
+ sell()
}
class Item {
- price
}
Shop *-- Item : have
Shop *-- Clerk : belong
Order *-- Item : include
}
package customer {
class Customer {
- wallet
+ buy()
}
}
Headquarter *-- Shop : belong
Customer --> Item : buy
このように、hide empty命令を効果的に使うことでグッとクラス図を読みやすくできる。
Discussion