PlantUMLの例を用いたUMLにおけるOCLの使い方についての包括的ガイド
導入

UML(統合モデル言語)は、システムの構造を可視化する点で優れているが、複雑なビジネスルールと意味論を正確に表現するには欠ける。ここにオブジェクト制約言語(OCL)が登場する。OCLは、UML図を厳密で曖昧さのないビジネスロジックに結びつける形式的な「接着剤」として機能する。
このガイドでは、OCLがUMLとどのように統合されるか、知っておくべき重要な概念を説明し、実用的なPlantUMLの例を提供して、これらの制約を可視化する。
1. 相乗効果:OCLがUMLを補完する方法
UMLをシステムの「骨格」(クラス、関連、状態)とし、OCLを「神経系」(骨格がどのように振る舞うかを規定するルール、条件、論理)と捉えるとよい。
UMLにおける視覚的表現
標準的なUMLでは、OCLの制約は通常、ノートを関連するモデル要素(クラス、操作、または状態)に付随させることで表現される。
-
ステレオタイプ: OCLを含むノートは、しばしば
{制約}ステレオタイプ。 -
コンテキスト: すべてのOCL式は
contextキーワードで始まり、そのルールが適用されるUML要素を宣言する。
2. UMLにおけるOCLの主要な概念
A. クラス不変条件(inv)
不変条件とは、必須となる条件であり、常に真であるクラスのすべてのインスタンスについて、呼び出される操作にかかわらず常に成り立つ。
重要な概念: 不変条件が違反された場合、システムは無効な状態にある。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4
class BankAccount {
- accountNumber: String
- balance: Real
- isOverdraftEnabled: Boolean
+ withdraw(amount: Real)
+ deposit(amount: Real)
}
note right of BankAccount
**クラス不変条件:**
context BankAccount
-- 残高は、 overdraftが有効でない限り負になることはない
inv balanceRule:
self.balance >= 0 or self.isOverdraftEnabled
-- overdraft限度額は500を超えることはできない
inv overdraftLimit:
self.isOverdraftEnabled implies self.balance >= -500
end note
@enduml
B. 操作契約(pre, post, body)
操作(メソッド)には、実行前後における振る舞いを定義する制約を設定できる。
-
事前条件(
pre):真でなければならないこと前に操作が開始される前に。(呼び出し側の責任) -
後条件(
post):真になること後に操作が終了した後。(操作の保証) -
本体(
body):クエリ操作の正確な戻り値を定義する。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**操作契約:**
-- 前提条件:カートは空であってはならない
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- 後条件:カートは空になり、領収書が生成される
post: self.items->isEmpty() and result <> null
-- 本体:クエリ操作の定義
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
C. 導出値および初期値(導出, 初期)
冗長なデータを保存する代わりに、UMLでは属性に {導出} または {初期}、OCLがその正確な値を定義する。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**導出値と初期値:**
-- 年齢を動的に計算
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- 作成時の初期状態
context Employee::isActive: Boolean
init: true
end note
@enduml
D. 状態機械のガード
状態機械図では、遷移にガード—遷移が発生するためには真で評価されなければならない論理式です。これらのガードを記述するための標準言語がOCLです。

@startuml
skinparam stateBackgroundColor #F3E5F5
[*] --> Created
Created --> PendingApproval : submit()
state PendingApproval {
}
PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]
Approved --> Shipped : ship()
Shipped --> [*]
note right of PendingApproval
**状態不変条件:**
context Order
inv: self.status = 'Pending' implies
self.submittedBy->notEmpty()
end note
note right of Approved
**approve()の後件条件:**
context Order::approve()
post: self.status = 'Approved' and
self.approvedDate <> null
end note
@enduml
3. 総合的な事例研究:企業モデル
OCLを、複雑なUMLクラス図に適用してみましょう。対象はEmployee, Department、およびProjectです。これにより、OCLが関連をどのように扱い、コレクションイテレータ(select, forAll, size).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D
class Employee {
- SSN: String
- firstName: String
- birthDate: Date
- hireDate: Date
- salary: Float
+ age(): Integer
}
class Department {
- number: Integer
- name: String
- locations: Set<String>
- {derived} nbrEmployees: Integer
}
class Project {
- number: Integer
- name: String
- location: String
}
Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates
note right of Employee
**複雑な不変条件とナビゲーション:**
context Employee
-- 1. 基本属性ルール
inv ageCheck: self.age() >= 18
-- 2. 関連のナビゲーション(多重度)
inv maxProjects: self.worksOn->size() <= 4
-- 3. イテレータの使用(forAll)
-- 上司は部下より先に雇用されなければならない
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. 跨関連ルール
-- 従業員は所属部署が管理するプロジェクトのみに従事できる
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. ユニーク識別子(キー)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implies e1.SSN <> e2.SSN
)
end note
note bottom of Department
**導出属性:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**プロジェクトの不変条件:**
context Project
-- プロジェクトの場所は、所属部署の場所の一つでなければならない
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. UMLにおける高度なOCL技術
A. 使用するlet可読性のため
OCL式が複雑になりすぎた場合は、制約内に局所変数を定義するためにletキーワードを使用してください。これにより、UMLノートの可読性が大幅に向上します。

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**複雑な論理に 'let' を使用する:**
context Employee
-- 合計時間の計算と境界値の確認
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 and totalHours <= 50
end note
@enduml
B. 後条件における以前の値の取り扱い(@pre)
後条件を定義する際、しばしば新しい状態と以前の状態を比較する必要があります。OCLでは、@pre後置記号を使用して、操作が開始される直前のプロパティの値にアクセスします。

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**後条件で @pre を使用する:**
context Company::hireEmployee(p: Employee)
-- 新しい従業員集合は、以前の集合に新しい人物を追加したもの
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. UMLにおけるOCLのベストプラクティス
-
制約に名前を付ける:常に不変条件や条件に名前を付けてください(例:
inv maxProjects:)。これにより、ドキュメントやトレーサビリティマトリクスで参照しやすくなります。 -
ノートを戦略的に使用する:PlantUMLや視覚的なUMLツールでは、クラスボックスにOCLを詰め込まないでください。OCLは隣接するノートに配置してください。これにより、図をきれいに保ち、読みやすくできます。
-
制約をしすぎない: UMLの多重性(例:)で簡単に表現できないルールのみにOCLを記述する。
0..1,1..*)または基本型。ルールを視覚的に表現できる場合は、視覚的に示す。 -
コレクションイテレータを活用する: マスターする
->select(),->forAll(),->exists()、および->collect()。これらはOCLにおけるUML関連のナビゲーションに最も強力なツールである。 -
nullや空集合の確認: オプションの関連をナビゲートする際(多重性
0..1または0..*)は、常に->notEmpty()または->isEmpty()プロパティにアクセスする前に使用し、未定義の評価エラーを回避する。














