オブジェクト制約言語(OCL)の基本UML BASIC LECTURE

» 2004年12月10日 12時00分 公開

今回はUMLよりOCL

 今回は、UMLではなくUMLモデルを補助しそのモデル要素にかかわる制約を正確に表現することを目的に導入されたOCL(Object Constraint Language:オブジェクト制約言語)について簡単にご紹介しましょう。

なぜUMLだけでは足りないのか

 皆さんの中にはUMLさえあれば、オブジェクト指向でモデルを完全に記述できるのではないかとお考えの人も多いでしょう。実際、UMLを利用することで、自然言語のあいまいさを減らして業務領域やシステム化対象の構造をより正確に表現できたり、逆にJavaで書いた何万行ものソースコードそのままよりは、それらのコードの構造や振る舞いを抽象化してビジュアルな全体感を持つのに有効そうと実感されているエンジニアの皆さんは多いと思います。

 しかし、ビジュアルなモデルだけでは表現できない内容が普通に存在します。それは、クラス図がインスタンスレベルの情報を捨象して表している、ということに起因します。例えば、クラスAとBの間に関連Rがあるというだけでは、インスタンスレベルでどのような数量的な対応関係なのかまったく分かりません。そこで、UMLでは多重度という表現を導入して、2つのクラス間の関連インスタンスの可能性の範囲を数値的に制約として表現しているのです。しかし、それでも複数の関連がかかわると単純に多重度だけでは手に負えなくなります。

 こんな例を考えてみましょう。「すべての定期券はその指定乗車駅と指定降車駅が異なる」。実はこんな当たり前の事実も、クラス図には描かれていません。「定期券」クラスと「駅」の間にロール乗車駅とロール降車駅をそれぞれ持つ2本の関連が引かれているだけで、『すべての……は異なる』のような量や相違に関する制約条件はUML図だけでは表せないのです。関連R1と関連R2を合成した関連の数量制約を説明するにはどうしても束縛変数を使った限定(論理学でいう「すべてのxがP:∀x.P(x)」「あるxがQ:∃x.Q(x)」を示す量化)が必要になってくるのです。

ALT 図1 定期券と駅

 ただし、OCLでは数学や論理学の苦手な人でも読み書きしやすいように、普通の英語の文章に近いキーワードと文法が定義されていますので、安心してください。いまの定期券の例では、「context 定期券inv: self.乗車駅 <> self.降車駅」と書けばいいのです。いま焦点を当てているモデル対象をcontextで指定することと、その対象内で必ず真になる式をinv: というキーワード(invariant:不変条件)の後に書くのだということ、またselfとはそのcontext自体の任意のインスタンスを指すことさえ知っていれば、式の意味はだいたい想像できるでしょう。では、簡単な例から徐々にOCLになじんでいきましょう。

簡単な制約を書いてみよう

 UMLを用いると、問題領域の状況をそこに登場する概念を示すクラスとそれらクラスの間の関連として、分かりやすく表すことができます。例えば、人がいて人はそれぞれ口座を持っている。ただし、口座を持っている人もいれば持っていない人もいる、というようなことは次のようなクラス図で表すことができます。

ALT 図2 銀行口座と人のクラス図(1)

 それでは、口座の属性である「残高:円」に関する制約を書いてみましょう(残高という属性名に対しその型が「円」クラスという想定です)。すべての口座は「その残高がマイナスになってはならない」という制約です。

context 口座 inv:
  self.残高 >= \0

 こんな感じで書けばよいですね。contextが口座と指定されているので、この式の中のself.は省略できますが、オブジェクト指向ではオブジェクトとしての自分を明確に意識することが重要なので、省略しないことをお勧めします。

 次に、このクラス図の範囲では、それぞれの口座の属する銀行はすべて「豆バンク」だとします。そのことは次のように表せます。

context 口座 inv:
  self.銀行名 = '豆バンク'

 ただし、もし本当にすべての口座インスタンスが共通の銀行名を持つのだとしたら、「口座」のクラス定義を見直した方がよいでしょうね。通常のインスタンススコープの変数ではなくクラススコープの変数(Java等でいうstatic)とすればよいですね。UMLではその場合、属性名にアンダーラインを付けて、銀行名:Stringとしなければなりません。

もう少し複雑な制約

 では今度は、人の年収:円によって貸出限度額:円が変化するという状況をOCLでモデル化してみましょう。具体的には次のような条件を考えてみます。

  • 年収200万円未満の人は、口座からの貸出限度額=\0
  • 年収200万円以上の人は、口座からの貸出限度額=\年収の1割(端数切り捨て)

 このような制約は一種のビジネスルールですが、OCLでは以下のようにif then else式を用いて書けます。

context 口座 inv:
  if self.名義人.年収 < \2,000,000 then
   self.貸出限度額 = \0
  else
   self.貸出限度額 = (self.名義人.年収 * 0.1) .round()
  endif

 せっかく貸出限度額を設定したので、この属性を利用して、貸出限度額分は自動的に残高がマイナスになることを許すように残高の制約を修正しておきましょう。

context 口座 inv:
  self.残高 + self.貸出限度額 >= \0

 このようにOCLはうまく利用すると業務モデリング、わけてもビジネスルールを正確に仕様化するという目的にも応用が開けるでしょう。

 ところで、このクラス図の人クラス側の多重度は「1」(厳密に1)になっています。つまり、どの口座もそれを保持しているのはただ1人の人に限るということがUMLの多重度の表現で定義できているわけです。

ALT 図3 銀行口座と人のクラス図(2)

 しかし、「すべての口座の口座番号は異ならなければならない」という制約を表現したいと思うと、もうUMLのクラス図だけでは表現できません。こんなときこそ、OCLの出番です。

口座.allInstances()->isUnique(口座番号)

 こう書くことで、口座クラスの全インスタンスはその属性である口座番号がダブってはいけないことが明示されます。allInstancesという操作はクラスに適用し、そのクラスに属するすべてのインスタンスの集合を返します。ここで使ったisUnique(expr)という操作は、同じ例を用いて示すと次のような式の省略形だと見なすことができます。

口座.allInstances()->forAll(a,b|a<>b implies a.口座番号<>b.口座番号)

 ここで、<>は不等号を表わし、impliesは「⇒;ならば」を意味する論理演算で、前提から結論が導出されることを表します。従って、この式は、異なる口座インスタンス同士には必ず異なる口座番号の値が設定されますよ、と宣言しているのと同じです。

 ただし、OCLで書くのが面倒くさいという人は、取りあえずは、「口座番号{unique}」というふうに属性の横に{}で囲って制約のキーワードを書いておけばよいでしょう。この{unique}という表現も実は、「isUnique(口座番号)」の省略形だと考えれば納得がいくでしょう。

ALT 図4 銀行口座と人のクラス図(3)

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ