de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

提供された講義資料に基づき、このガイドはUMLモデリングの文脈におけるオブジェクト制約言語(OCL)の基本的な概念、構文、および実用的な応用をカバーしています。

Objct Constraint Language (OCL)


1. OCLの紹介

なぜOCLが必要なのか?

UML図(クラス図など)は構造を可視化するのに優れているが、仕様のすべての関連する側面を表現することはできない。

  • 自然言語は曖昧である。

  • 従来の形式言語は曖昧さがないが、平均的なシステムモデラーが読み書きするにはあまりに難解である。

  • OCLはこのギャップを埋める:それは形式言語正確な制約を表現するために使用されるが、依然として読み書きしやすい.

OCLの核心的な特徴

  1. 純粋な式言語:式には副作用がない。OCL式の評価は値を返すが、システムの状態を変更することはできない。ただし、状態変化を記述することはできる(例えば、事後条件において)。記述する状態変化(例えば、事後条件において)を記述する。

  2. プログラミング言語ではない:プログラム論理、制御フローを書くことも、状態を変更するプロセス/操作を呼び出すこともできない。

  3. 型付き言語:すべての式には型がある。整合性のある式は型適合ルールに従わなければならない。すべてのUML分類子はOCLの型である。

  4. 即時評価:オブジェクトの状態は評価中に変化してはならない。評価中OCL式の評価中


2. 基本的な構文と意味

コンテキストとSelf

すべてのOCL式は、特定のUML分類子(クラス、インターフェースなど)のコンテキストの中で評価される。

  • context: 制約が適用されるクラスを定義する。

  • self: 評価中のコンテキストクラスの特定のインスタンスを指す予約語。

context Person
-- 'self' はPersonの特定のインスタンスを指す

コメントと演算子

  • コメント: は で示される-- (例: -- これはコメントです).

  • 中置演算子: 標準的な数学/論理演算子(+-=<)を用いることができる。概念的には、a + b は と同一であるa.+(b).

プロパティへのアクセス

属性や操作にアクセスするにはドット表記を使用してください。操作にはパラメータがなくても、かならず括弧を使用してください。

コンテキスト Person
self.age             -- 'age' 属性にアクセス
self.stockPrice()    -- 'stockPrice' 操作を呼び出し

3. 型とコレクション

基本的な事前定義型

OCLには、特定の操作を持つ標準型が含まれています:

  • 論理型: truefalse(操作:andornotimpliesxor)

  • 整数型/実数型:数値(操作:+-*/絶対値最大値最小値除算剰余)

  • 文字列: テキスト(操作: サイズ連結部分文字列整数に変換)

コレクション

コレクションは複数の要素を保持するために使用される抽象型です。それらは -> 表記を使ってアクセスされます。

  1. 集合: 数学的な集合(重複なし、順序なし)。 集合 {1, 2, 5}

  2. 順序付き集合: 重複なし、ただし位置順に並べられます。 順序付き集合 {5, 4, 3}

  3. バッグ: 重複を許し、順序は関係ない。 バッグ {1, 2, 2, 5}

  4. シーケンス: 重複を許し、順序が関係する。 シーケンス {1, 2, 5, 10}

    • 注意: この .. 表記は連続する整数のシーケンスを作成する: シーケンス {1..5}.

主要なコレクション操作

  • 共通: size()isEmpty()notEmpty()includes(v)excludes(v)count(v)sum().

  • イテレータ(OCLにとって重要):

    • select(expr): 要素を返します。ここで expr は真です。

    • reject(expr): 要素を返します。ここで expr は偽です。

    • collect(expr): すべての要素から特定のプロパティを抽出し、新しいコレクションにします。

    • forAll(expr): を返します true if expr は少なくとも1つの要素に対して真です。すべての 要素に対して真です。

    • exists(expr): を返します true if expr は少なくとも1つの要素に対して真です。少なくとも1つの 要素に対して真です。

    • iterate(elem; acc = init | expr): すべてのイテレータの汎用的な基底です。

イテレータの構文のバリエーション:

self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)

4. UMLモデルのナビゲーション

OCLは、関連をたどるためのナビゲーション言語として広く使用されています。

標準ナビゲーション

逆のロール名を使用してナビゲートします。戻り値の型は多重度に依存します:

  • 多重度 1 または 0..1: 単一のオブジェクトを返します。

  • 多重度 *: を返します Set (または OrderedSet がマークされている場合 {ordered}).

context Company
self.manager.age > 40               -- managerは0..1、Personオブジェクトを返す
self.employee->notEmpty()           -- employeeは0..*、Setを返す

ロール名が欠けている場合は、もう一方の端のクラス名の小文字を使用します(例: self.bank).

関連クラス

関連クラスの小文字名を使用して、関連クラスにナビゲートします。

context Person
self.job->size() >= 1  -- 'job'はPersonとCompanyの間の関連クラスです

再帰的関連

関連が同じクラスに戻る場合(例:2人のPerson間の結婚)、四角括弧を使用して方向を指定する必要があります [roleName].

コンテキスト Person
self.marriage[wife]->select(m | m.ended = false)->size() = 1

限定付き関連

特定のオブジェクトを選択するために、質問属性を角かっこ内に使用します。

コンテキスト Bank
self.customer[12345]  -- accountNo 12345 の特定の Person を返す
self.customer         -- すべての顧客の Set を返す

再型変換(キャスト)

使用する oclAsType() サブタイプまたは隠されたスーパークラスのプロパティにアクセスするために使用する。

self.oclAsType(Sub).p       -- Subtype で定義されたプロパティ 'p' にアクセス
self.oclAsType(Super).p     -- Superclass で定義されたプロパティ 'p' にアクセス

5. 制約の記述

1. 不変条件(inv)

すべてのクラスのインスタンスに対して常にtrue でなければならない条件。 クラスのすべてのインスタンスに対して常に真でなければならない条件。

コンテキスト Company 
inv: self.noEmployees <= 50
inv SME: self.stockPrice() > 0  -- 'SME' は制約のオプション名

2. 前提条件および後置条件(prepost)

操作に関連する制約。

  • pre: 実行の に真でなければならない。

  • post: 実行の実行。使用する結果戻り値のため

  • @pre:後条件でプロパティの値を参照するために使用操作が開始される前操作が開始された後

コンテキスト Person::income(): Integer
pre adult: self.age >= 18
post resultOK: result < 5000

コンテキスト Company::hireEmployee(p: Person)
post: employee = employee@pre->including(p)

3. 本体、初期値、導出式

  • 本体:クエリ操作の結果を定義する(再帰的である可能性がある)

  • 初期値:属性の初期値を定義する

  • 導出:導出属性を定義する

コンテキスト Person::income(): Integer
本体: self.job.salary->sum()

コンテキスト Person::isMarried: Boolean
初期値: false

コンテキスト Company::noEmployees: Integer
導出: self.employee->size()

6. 高度なOCL機能

letとdef式

  • let:単一の式内でローカル変数を定義する

    let totHours: Integer = self.worksOn->collect(hours)->sum() in totHours >= 30
    
  • def:分類子に付随する再利用可能な変数または操作を定義する

    def: hasTitle(t: String): Boolean = self.job->exists(title = t)
    

未定義値(3値論理)

部分式が未定義の場合(例:nullの関連をたどる場合)、全体の式も未定義になる。ただし、論理演算子には短絡評価の例外がある:

  • true or <未定義> = true

  • false and <未定義> = false

  • false は <undefined> を含意する = true

  • <undefined> は true を含意する = true

クラスの特徴と事前に定義されたオブジェクトプロパティ

  • allInstances(): クラスのすべての既存インスタンスからなる集合を返す。

    Person.allInstances()->size() <= 100
    
  • オブジェクトプロパティ: oclIsTypeOf(Type)oclIsKindOf(Type)oclInState(State)oclIsNew (事後条件で使用される)。


7. 総合的な事例研究:企業モデル

これらの概念を定着させるために、企業データベースモデル(従業員、部門、プロジェクト、従属者)に適用された整合性制約の集まりを示す。

基本的な属性制約

-- 年齢は18以上でなければならない
context Employee inv: self.age() >= 18

-- 雇用日は誕生日より後になければならない
context Employee inv: self.hireDate > self.birthDate

-- 管理者の開始日は雇用日より後になければならない
context Employee inv: self.manages->notEmpty() ならば self.manages.startDate > self.hireDate

ナビゲーションおよび関係性制約

-- サーバーは従業員より年上で、給与も高い必要がある
context Employee 
inv: self.supervisor->notEmpty() ならば self.age() > self.supervisor.age()
inv: self.supervisor->notEmpty() ならば self.salary < self.supervisor.salary

-- サーバーは部下より前に雇用されなければならない
context Employee 
inv: self.subordinates->notEmpty() ならば self.subordinates->forall(e | e.hireDate > self.hireDate)

-- 部門の管理者はその部門の従業員でなければならない
context Department 
inv: self.worksFor->includes(self.manages.employee)

-- プロジェクトの場所は部門の場所の一つでなければならない
context Project 
inv: self.controls.locations->includes(self.location)

-- 従業員は所属部門が管理するプロジェクトのみに従事できる
context Employee 
inv: self.worksFor.controls->includesAll(self.worksOn.project)

コレクションおよびイテレータ制約

-- SSNはすべての従業員において一意の識別子でなければならない
context Employee 
inv: Employee.allInstances->forAll(e1, e2 | e1 <> e2 ならば e1.SSN <> e2.SSN)

-- 従属者の名前と関係は、各従業員ごとに一意でなければならない
context Employee 
inv: self.dependents->forAll(e1, e2 | 
      e1 <> e2 ならば (e1.name <> e2.name または e1.relationship <> e2.relationship))

-- 派生属性:部門内の従業員数
context Department 
inv: self.nbrEmployees = self.worksFor->size()

-- 従業員は最大4つのプロジェクトに従事できる
context Employee 
inv: self.worksOn->size() <= 4

複雑な論理を使用して let and select

-- 週あたりの合計勤務時間は30~50時間の間でなければならない
context Employee 
inv: let totHours: Integer = self.worksOn->collect(hours)->sum() 
     in totHours >= 30 and totHours <= 50

-- 特定のプロジェクトで10時間未満勤務する従業員は最大2人まで
context Project 
inv: self.worksOn->select(hours < 10)->size() <= 2

-- プロジェクトで5時間未満勤務できるのは、部門マネージャーのみ
context Employee 
inv: self.worksOn->select(hours < 5)->notEmpty() implies 
     self.worksFor.manages.employee = self

-- 従業員が部下を持たない場合、すべてのプロジェクトで10時間以上勤務しなければならない
context Employee 
inv: self.subordinates->isEmpty() implies self.worksOn->forAll(hours >= 10)

-- 部門マネージャーは、その部門が管理するすべてのプロジェクトで5時間以上勤務しなければならない
context Department 
inv: self.controls->forall(p: Project | 
      self.manages.employee.worksOn->select(hours >= 5)->contains(p))

循環の防止(再帰的論理)

-- 従業員自身を監督することはできない
context Employee inv: self.subordinates->excludes(self)

-- 監督構造は循環してはならない
context Employee 
def: allSubordinates = self.subordinates->union(
          self.subordinates->collect(e: Employee | e.allSubordinates))
inv: self.allSubordinates->excludes(self)