PlantUML デザインパターン

# はじめに

[PlantUML](https://plantuml.com/ja/)は、テキストベースでUMLを記述するための、おそらく世界で最も普及したツール。

テキストベースなので、gitを使ったUMLの変更管理などが容易で、マウスを使わずにUMLをさくっと書けるメリットがある。
ここでは、PlantUMLを記述する上で参考になるデザインパターンを紹介する。

1. パターン未適用  
   デザインパターン未適用の状態のPlantUMLファイルとその課題を述べる。
2. パターン適用  
   デザインパターンを適用したPlantUMLファイルとその利点を述べる。

# Skinparam分離パターン

## パターン未適用

PlantUMLファイルのデザインの設定(sksinparam)を個別のクラス図ファイルの中に記載している。
ファイルが多くなるほど、共通的なデザインの設定を管理しにくくなる。

```plantuml
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
left to right direction

package shop {
  class Shop
  Shop *-- Item
}
```

```plantuml
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true

package customer {
  class Customer
  Customer --> Item : buy
}
```
```php:shop-class.puml
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
# 個別設定
left to right direction

package shop {
  class Shop
  Shop *-- Item
}
```


```php:customer-class.puml
# 共通設定
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`で管理する。

```php
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
# 個別設定
left to right direction
```

```plantuml
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
left to right direction

package shop {
  class Shop
  Shop *-- Item
}
```

```plantuml
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true

package customer {
  class Customer
  Customer --> Item : buy
}
```

```php:skinparam.puml
# 共通設定
skinparam linetype ortho
skinparam shadowing false
skinparam handwritten true
```

```php:shop-class.puml
# 共通設定
!include skinparam.puml
# 個別設定
left to right direction

package shop {
  class Shop
  Shop *-- Item
}
```

```php:customer-class.puml
# 共通設定
!include skinparam.puml

package customer {
  class Customer
  Customer --> Item : buy
}
```


クラス増加時にもファイルが肥大化しにくく、メンテナンス性を維持できる。

# Packageパターン

## パターン未適用

1つのPlantUmlファイルに複数のパッケージが定義される。

```plantuml
package shop {
  class Shop
  Shop *-- Item
}

package customer {
  class Customer
  Customer --> Item : buy
}
```

```php:パターン未適用
package shop {
  class Shop
  Shop *-- Item
}

package customer {
  class Customer
  Customer --> Item : buy
}
```

クラスが増えるほどファイルが肥大化してメンテナンス性が劣化する。

## パターン適用

パッケージ毎にファイルを分ける。

```plantuml
package shop {
  class Shop
  Shop *-- Item
}
```

```plantuml
package customer {
  class Customer
  Customer --> Item : buy
}
```

```php:shop.puml
package shop {
  class Shop
  Shop *-- Item
}
```

```php:customer.puml
package customer {
  class Customer
  Customer --> Item : buy
}
```

クラス増加時にもファイルが肥大化しにくく、メンテナンス性を維持できる。

# Arrowパターン

## パターン未適用

オブジェクト間の依存関係を`extends`や`implements`といったキーワードで記述する。

```plantuml
class Parent
class Child extends Parent
```

```php:Arrowパターン未適用
class Parent
class Child extends Parent
```

この表現には以下の問題がある。

- 矢印向きを指定できない。
- 個々のオブジェクトを表現する`class`宣言と、"矢印"というレイアウトを指定する`extends`キーワードが混ざっている。

## パターン適用

Arrowパターンを適用した例を以下に示す。

```plantuml
class Parent
class Child

Parent <|-up- Child
```

```php:Arrowパターン適用
class Parent
class Child

Parent <|-up- Child
```

Arrowパターンは個々のオブジェクトのコンテキストを保持する`class`宣言からレイアウト情報を保持する矢印を分離する。

コンテキストとレイアウトの分離により、より柔軟な図表現が可能となる。上記の例では矢印に`up`キーワードを使うことで矢印の向きを逆向きにした。

# Includeパターン

## パターン未適用

`start/endsub` と `includesub` キーワードをつかって、PlantUMLファイルをオブジェクト指向の構造にまとめよう。

次のようなUMLを書くことを考えてみよう。

```plantuml
!startsub interface
package activities {
  class Activity
}
!endsub

!startsub interface
abstract class ActivityEdge
!endsub

Activity "0..1" *-- "*" ActivityEdge
```

`Activity`クラスは`activities`パッケージの一部であり、アブストラクトクラス`ActivityEdge`を集約する。このUMLを、１つのPlantUMLファイルに記述すると次のようになるだろう。

```php:all.puml
@startuml

package activities {
  class Activity
}

abstract class ActivityEdge

Activity "0..1" *-- "*" ActivityEdge

@enduml
```

このとき、`activities`パッケージ内の`Activity`クラスとパッケージ外の`ActivityEdge`クラスが、同じファイル内に書かれることになる。

こうすると、たとえばactivitiesパッケージだけを図示したい場合や、他のクラス図にActivityクラスを載せたい場合にコードクローンするしかなく、不便である。

## パターン適用例

ここで使えるのが、`start/endsub` と `includesub` キーワードである。前述のPlantUMLファイルをこのキーワードを使って2つのファイルに分割する。

```php:activity.puml
@startuml

!startsub interface
package activities {
  class Activity
}
!endsub

@enduml
```

```php:index.puml
@startuml

!includesub activity.puml!interface

!startsub interface
abstract class ActivityEdge
!endsub

Activity "0..1" *-- "*" ActivityEdge

@enduml
```

感嘆符`!`は、PlantUMLの[プリプロセッサ命令](https://plantuml.com/ja/preprocessing)を意味する記号である。

`activity.puml`の`!startsub interface ... !endsub`は、`interface`という名前のサブパートを宣言するプリプロセッサ命令である。このサブパートは、`!includesub`キーワードを使って、別のPlantUmlから呼び出すことができる。

`index.puml`の`!includesub activity.puml!interface`は、`activity.puml`の`interface`サブパートを呼び出すプリプロセッサ命令である。

このように、`start/endsub` と `includesub` キーワードを用いて`activities`パッケージを再利用しやすい別のファイルに分割することができた。


# Compactパターン

## パターン未適用

フィールドやメソッドを持たないクラスまで、フィールドとメソッドのセクションが表示されて見にくい。
たとえば、フィールドを持たないClerkクラスと、メソッドを持たないItemクラスがあったとする。

```plantuml
class Clerk {
  + sell()
}

class Item {
  - price
}
```

```php:Compactパターン未適用
class Clerk {
  + sell()
}

class Item {
  - price
}
```

PlantUML は、デフォルトではフィールド、メソッドを持たない場合もセクションを表示してしまう。
クラスが1つ、2つの場合はまだいいが、クラスが増えてきたらどうだろうか。

```plantuml
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
```

```php:Compactパターン未適用
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行を追加する。

```php
hide empty fields
hide empty methods
```

1行目は、空のフィールドを持つセクションを非表示にするスキンパラメータである。
2行目は、空のメソッドを持つセクションを非表示にするスキンパラメータである。

適用例を以下に示す。

```plantuml
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
```

```php:Compactパターン適用
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命令を効果的に使うことでグッとクラス図を読みやすくできる。