EurekaMoments

ロボットや自動車の自律移動に関する知識や技術、プログラミング、ソフトウェア開発について勉強したことをメモするブログ

PlantUMLを通じてクラス図の書き方を学ぶ

目次

はじめに

ソフトウェアの仕様書、設計書の作成や管理を効率化するために、Markdown + PlantUMLによる作成方法を日々模索しています。
しかしながら、そもそもUML図の正式な書き方というものをちゃんと分かっていないというのが正直なところなので、PlantUMLを通じてUMLの各種図の書き方を勉強していきます。

  • 本稿のテーマは、「UMLによるクラス図の書き方」です。
  • 本稿におけるUML図の作成は、Markdown + PlantUMLがベースであることを前提とします。

Markdown + PlantUMLでドキュメントを作成する環境の構築方法については、下記の記事を参照ください。

www.eureka-moments-blog.com

クラス図とは

  • モデルの静的な構造を表す図であり、問題領域やシステムの構造を表現する。
  • 全体、パッケージ単位、機能単位など、様々な視点で作成できる。

クラス図を作るまでの流れ

  1. 作りたいソフト(システム)の全体像を決めて、あらゆるモノの名前を抽出する。
  2. 抽出したモノをオブジェクトとしてオブジェクト図を描く。関連するものは線で結ぶ。同じカテゴリのオブジェクトはパッケージにまとめる。
  3. オブジェクト図は「クラス図の設計図」なので、この時点ではラフな描き方でよい。
  4. オブジェクトのクラスを作成する。抽象化、モジュール化、分かりやすさを意識する。
  5. 抽象度の高いクラスは汎用性が高いので、様々なところで使える共有モジュールになれる。
  6. 抽象度が低い専用クラスを作って、シンプルな構成にするという手もある。
  7. クラス名だけを見て、どんなモジュールか把握できるのが理想。
  8. クラスの責務(属性と操作)を考える。操作から別のクラスを考える方がいい場合もある。
  9. 責務が多すぎるファットクラスや、あらゆるクラスと関連を持つ神様クラスが存在するソフトはメンテナンス性が低い

クラス図を構成する要素

クラス(Class)

  • 長方形の中を3区画に分割し、上からクラス名属性操作を記述する。
  • クラス名の上には"<<>>"で囲まれたステレオタイプを付ける事ができる。
  • 属性はデータを表し、可視性 名前:型 = デフォルト値という形式で書く。
  • 操作は振る舞いを表し可視性 名前(パラメータ):返り値という形式で表す。
  • パラメータは、名前:型=デフォルト値をコンマで区切って並べる。staticな属性や操作は下線を付けて表現する。

可視性は下記の表のパターンがある。 f:id:sy4310:20181225185945p:plain

これらをPlantUMLで書くと下記の表のようになる。 f:id:sy4310:20181225190034p:plain

PlantUMLによるクラス図の記法は下記の通り。

class Dummy{
    - field1
    # field2
    {static} field3
    ~ method1()
    + method2()
}

このコードによって下記のようなクラス図が作成される。
f:id:sy4310:20181225190152p:plain

PlantUMLによるアイコン表示を適用したくない場合は、コマンド skinparam classAttributeIconSize 0をコードの先頭に記述する。

skinparam classAttributeIconSize 0

class Dummy{
    - field1
    # field2
    {static} field3
    ~ method1()
    + method2()
}

f:id:sy4310:20181225190500p:plain

クラス間の関係と線種

  • PlantUMLでは、クラス間の関係を下記の形式で記述する。
クラス名 線種の記号 クラス名 [: ラベル]
  • クラス名に英数字以外を使いたい場合は"(ダブルクオテーション)で囲む。
  • 日本語の場合は " がないとうまく表示されない場合があるので、囲っておいた方が安全である。

  • PlantUMLでの記述例は下記の通り。

ClassA -- ClassB
"クラスC" -- "クラスD" : 関係

f:id:sy4310:20181225190743p:plain

  • ラベルの最初または最後に < か > を使って、他のオブジェクトへの関係を示す矢印を追加できる。
class Car

Driver - Car : drives >
Car *- Wheel : have 4 >
Car -- Person : < owns

f:id:sy4310:20181225190846p:plain

  • :に続けてフィールド名やメソッド名を記述すると、フィールドやメソッドを宣言できる。
Object <|-- ArrayList

Object : equals()
ArrayList : Object[] elementData
ArrayList : size()

f:id:sy4310:20181225190950p:plain

  • クラス間の関係を表す線種は下記の表のようになる。 f:id:sy4310:20181225191021p:plain

関連(Association)

  • クラス間に、参照や実体を保持するなどの関係があることを表す。
  • 線の両端には矢印をつけることができ、矢印がある場合はその方向にのみ関連があることを示す。
  • これを誘導可能性(Navigability)と言い、矢印の無い関連は誘導可能性が未知、あるいは双方向を意味する。
Control -- View
Control --> Model

f:id:sy4310:20181225191233p:plain

汎化(Generalization)と特化(Specialization)

  • 異なるクラス間で共通している部分を見出す事を汎化という。
  • 共通していない個別の性質について考える事を特化という。
  • 汎化を意識して共通部分を別に作る実装手段が継承である。

車クラスを継承して、バスクラスとトラッククラスが作られる例。

skinparam classAttributeIconSize 0

Car <|--- Bus
Car <|--- Truck

Car : - marker
Car : - type
Car : + Drive()
Car : + Turn()
Car : + Stop()

Bus : - drivingRoot
Bus : - fare
Bus : + Announce()
Bus : + OpenCloseDoor()

Truck : - maximumLoad
Truck : + Stack()
Truck : + Unload()

f:id:sy4310:20181225191415p:plain

実現(Realization)

  • Javaでいうinterfaceと同等の意味。
  • 相手を具象化する関係の際に用いる表現。
  • インターフェースとは、定数を指定したメンバ変数と抽象メソッドで構成され、クラスに実装して使用する。

実現を表すクラス図の例

skinparam classAttributeIconSize 0

class Operation <<interface>>
Operation <|... Plane
Operation <|... Ship
Operation <|... Car

Operation : + Drive()
Operation : + Stop()

f:id:sy4310:20181225191701p:plain

集約(Aggregation)と合成(Composition)

  • 集約は関連の一種で、オブジェクトが複数集まって全体を構成する関係である。
  • 双方のオブジェクト間に「全体 - 部分」の関係が成り立っている。
  • 合成は関連の一種で集約よりも結びつきが強い場合、例えば「全体 - 部分」において双方が存在して初めて成り立つような関係のときに用いられる。

集約を表すクラス図の例

skinparam classAttributeIconSize 0
Taxi o-- Crew

Taxi : - companyName
Taxi : - startingFare

f:id:sy4310:20181225191755p:plain

合成を表すクラス図の例(タイヤやエンジンがないと車が動かない)
f:id:sy4310:20181225191842p:plain

依存(Dependency)

  • 相手の状態、もしくはイベントに対して影響を受ける関係のときに用いる表現である。
  • 変更すると他方にも変更が生じるような関係。

依存を表すクラス図の例

FuelMeter ..> FuelTank : Dependency

f:id:sy4310:20181225194745p:plain

skinparam classAttributeIconSize 0

F1Driver ..> F1Machine

F1Driver : - nationality
F1Driver : - age
F1Driver : - career
F1Driver : + Drive()

F1Machine : - team
F1Machine : - sponsor
F1Machine : + Drive()
F1Machine : + Turn()
F1Machine : + Stop()

f:id:sy4310:20181225194831p:plain

ロール名(Role name)

  • 関連の端に書かれる、関連先の役割を表す名前。
  • 設計レベルでは、関連先を保持するための名前、つまり属性名を付けることが多い。

多重度(Multiplicity)

  • 関連の端に書かれ、関連の張られたオブジェクト間の数的関係を表す。
  • 具体的な数値の他に、"0..n"や"*"(共に0以上)、"1..n"(1以上)、"2..7"(2から7)のように任意の値を設定できる。
クラス名 "多重度" 線種 "多重度" クラス名 [: ラベル]

多重度の表記パターン
f:id:sy4310:20181225194932p:plain

多重度を記入したクラス図の例

Car "1" *--- "4" Tire
Car "1" o--- "0..*" ParkingLot : 1 - Many

f:id:sy4310:20181225195009p:plain

ステレオタイプ(Stereotype)

  • UMLの拡張メカニズムで、アプリケーションや問題領域固有の意味を、わかりやすくモデルに表現するために付加する文字列。
  • 同一のモデル要素を、意味的に分類することができる。
  • 例えば、interfaceのステレオタイプをつけたクラスは、振る舞いのインターフェースのみ定義したクラスであることを明示する。

制約(Constraint)

  • {}に囲んで制約を課すことができる。

下記は、コントローラに与えられるデータが年月日によって整列された状態で保持される場合の例。

skinparam classAttributeIconSize 0

Controller "1" o--> "0..n" Data : {sort by date}

Data : - id
Data : - date
Data : - arrivalTime
Data : - leavingTime

f:id:sy4310:20181225195249p:plain

特殊クラス

f:id:sy4310:20181225195317p:plain

enum FigureType {
    unknown = -1
    rectangle
    polygon
    ellipse
}

abstract Figure

class Rectangle

interface Comparable {
    {abstract} int compare(Comparable *other)
}

Figure <|-- Rectangle
Rectangle .|> Comparable

f:id:sy4310:20181225195407p:plain

ロリポップ

  • インターフェースを提供しているということを強調する場合は、ロリポップ(棒付きキャンディー)で省略表記することがある。
  • PlantUMLでは、関係の線種に ()-- を使う。
Comparable ()-- RationalNumber

f:id:sy4310:20181225195439p:plain

パッケージ

パッケージの定義

package "Package 1" {
    ClassA <|-- CLassB
}

package "Package 2" {
    ClassA <|-- ClassC
    ClassC *- ClassD
}

f:id:sy4310:20181225195644p:plain

パッケージスタイル

  • スタイルのデフォルトはフォルダスタイル。
  • ステレオタイプで別のタイプを指定し、形状を変更することができる。
package NodeStyle <<Node>> {
    class Class1
}

package FrameStyle <<Frame>> {
    class Class2
}

package CloudStyle <<Cloud>> {
    class Class3
}

package DatabaseStyle <<Database>> {
    class Class4
}

package FolderStyle <<Folder>> {
    class Class5
}

package RectStyle <<Rectangle>> {
    class Class6
}

f:id:sy4310:20181225195748p:plain

ネームスペース

  • 同じ名前のクラス名をネームスペースで分けて定義するためにnamespaceキーワードを使用する。
  • namespaceキーワードは、packageと同じスタイルで記述する。
  • ネームスペースをまたいでクラス関係を記述する場合は、ピリオドでつないで"ネームスペース名"."クラス名"という形式でクラスを指定する。
abstract class AbstPerson

namespace Space1 {
    .AbstPerson <|-- Person
    Meeting o-- Person
    .AbstPerson <|-- Meeting
}

namespace Space2 {
    Space1.Person <|-- Person
    .AbstPerson <|-- Person
    Space1.Meeting o-- Person
}

AbstPerson <|-- Space3.Person

f:id:sy4310:20181225200033p:plain

ノート

クラスに対するノート

クラスに対してノート(コメント)を付けることができる。

note position of クラス名 : コメント
クラスの定義
note position : コメント

positionには下記のキーワードから指定する。

  • top
  • bottom
  • left
  • right
class Class1
class Class2

Class1 - Class2

note top of Class1 : Class1の上側に\nコメント表示
note left of Class1 : Class1の左側に\nコメント表示
note bottom of Class2 : Class2の下側に\nコメント表示
note right of Class2 : Class2の右側に\nコメント表示

f:id:sy4310:20181225200239p:plain

関係を使ったノート

別名のつけてノートの要素を作り、それをクラスにリンクさせることでコメントを表示する。

class Class1
class Class2

Class1 <|-- Class2

note "Class1に対するコメント" as Note1
Class1 . Note1

note "Class1と2の両方に\n対するコメント" as Note2
Class1 .. Note2
Note2 .. Class2

note "対象をしてしなければ\nフローティングになる" as Note3

f:id:sy4310:20181225200332p:plain

リンクへのノート

クラスの関係の記述の直下にノートを書くと関係のリンクへのノートになる。

クラス関係の記述
note position on link : コメント
class Class1

Class1 --> Class2 : link 1
note on link : link1に対する\nコメント

Class1 --> Class3 : link 2
note right on link : link2に対する\nコメント

f:id:sy4310:20181225200426p:plain

オブジェクト図

  • classの代わりにobjectを使うとオブジェクト図となる。
  • ブロックによる定義では英数字しか使えない。
  • それ以外の文字を使いたい場合は一度asで別名を付けた後にメンバを追加する。
  • クラスという集合の中にオブジェクトが属していると考える。
  • クラス図だけでは抽象的な図しか書けないので、オブジェクト図でもっと詳しい補足をする。
object Object1 {
    id = 1234
    name = "Alice"
}

object "<u>bar:Object2</u>" as bar
bar : id = 4321
bar : name = "Bob"

f:id:sy4310:20181225200731p:plain

パッケージ図

  • パッケージ同士の依存関係を描画することで論理的なグルーピングをするための図。
  • インポートの場合は対象のパッケージや要素が取り込まれるが、アクセスの場合は取り込まない。
  • 呼び出すことができるのは、インポートするパッケージのみ。
  • PlantUMLにはパッケージ図が用意されていない。
  • パッケージ - クラス間のリンクはできない。
package Search{
}

package Drawing{
}

Search .> Drawing : <<import>>

package "Input/Output" as io{
}

Search ..> io : <<access>>

package Database{
}

io +-- Database

f:id:sy4310:20181225200918p:plain

GitHub

ここまで記述したPlantUMLのコードや、Markdownドキュメント(.md, .html, .pdf)は下記のGitHubリポジトリで公開しています。 github.com

参考資料