前回「第7回 オブジェクト制約言語の基本」まで見てきた例でOCLのイメージはつかんでいただけたのではないでしょうか。基本的に、OCLはUMLで書かれたモデル上を起点のモデル要素から属性や関連端(ロール名)を「.」(ドット)でたどる(ナビゲートする)ことによって、モデル要素間で満たさなければならない等・不等関係や数量的な大小関係、リンクやインスタンスの集合の数(collection->size())や包含関係に関する制約を表すものです。いってみれば、SQLのようなノリでオブジェクトモデル上でドット記法を用いてクエリーを定義し、それらのクエリーの間で満たすべき条件を書く、そんな使い方がOCLの一般的な利用法です。
OCLでは操作を適用する際に、2つの記法があって、適用対象がオブジェクトかそれともコレクション(オブジェクトの集合)かによって、「.」「->」を使い分けます。
obj.op1() オブジェクトobjに操作op1()を適用 col->op2() コレクションcolに集合操作op2()を適用
本文で紹介できなかったOCL式で重要なものをいくつか補っておきましょう。まず、制約の記述のスタイルですが、以下の2つが基本です。
・クラス不変表明(invariant)の場合
context クラス名 ( inv [ 制約名 ] : 条件式 )+
・事前条件(precondition)、事後条件(postcondition)の場合
context クラス名::操作名(引数リスト) [ : 返値型 ] ( { pre | post } [ 制約名 ] : 条件式 )+
UMLのノート中に記述する場合、context部分は省略可能です。次に、OCLで使える型(タイプ)ですが、次のようなものが利用可能です。
基本型 | Boolean、 Integer、 Real、 String |
Collection型 | Collection、 Set、 Bag、 Sequence |
その他 | OclType:メタクラスに相当する型 |
OclAny:基本型とすべてのUML型の上位クラス | |
Enumeration:列挙型 | |
OclExpression:OCLの条件式を表す型 | |
OclState:オブジェクトの状態を表す仮想型 |
OCLによるクエリーの対象や返ってくる値は上記の型(タイプ)かそのサブ型(サブタイプ)に属することになります。これ以上の詳細はぜひOCL仕様書を勉強してみてください。http://www.klasse.nl/ocl/ocl-subm.htmlからダウンロードできます。
ここでは振る舞いの仕様記述にOCLがどのように使えるかを見てみましょう。
振る舞いのような動的な対象を人間はそのままではうまく表現できません。そこで、振る舞いの実行前の静止状態と実行後の静止状態を比較することで間接的に振る舞いの特性や効果を語る、という方式を取るのです。ダイエット器具の利用前と利用後の2枚のカラダの写真を比較表示するという例のやり方と同じです。
それでは、「口座」クラスに対して「出金( 引出者:人,金額:円 ) :円」という操作を定義してみましょう。引数を2つ取り、引出者がどの人インスタンスで、出金の金額がいくらかを指定します。この操作の実行が成功するためには、引数「引出者」が正当な人であること、つまり、この口座の名義人であることが必要です。
おおよそ次のようなOCLでこの出金という操作は定義できます。
context 口座::出金( 引出者:人,金額: 円 ) :円 pre: 引出者 = self.名義人 and self.残高 + self.貸出限度額 >= 金額 post: self.残高 = self.残高@pre - 金額 and result = 金額
「pre:」でこの操作の事前条件を、「post:」でこの操作の事後条件を指定します。事前条件とは、その操作を呼び出す時点で真になっていなければいけない条件のセットで、これによって操作実行に当たっての前提を指定します。この例では、引出者がその口座の名義人と一致していることと、貸出限度額を前提に出金に必要な残高があるかを確認しています。これらが両方とも真のときにのみ、この操作は実行可能です。満たしていなければ、呼び出した時点でエラーになります。
事後条件とは、その操作が成功裏に実行された場合に満足され真になっているはずの条件のセットで、これによって操作の効果を宣言します。この例の場合には、残高から出金する金額をマイナスすると同時に、resultというOCLキーワードに金額:円の値をセットして返します。
事後条件post:において利用されている「@pre」というキーワードを後ろに付けることでその操作の実行前の値を参照します。これによって、事後条件における「残高」と事前条件の値「残高@pre」の両方に言及することができ、両者の実行前後の関係を指定できるというわけです。「result」はその操作を関数として取り扱いたい場合の戻り値を指定するために参照するキーワードです。
以上述べたような事前条件と事後条件を用いた操作の仕様定義は、DbC(Design by Contract: 契約による設計)と呼ばれることがあります。つまり、定義しようとしている操作f()の呼び出し元であるクライアント側と、その操作f()を実行しサービスを提供するサプライヤー側との間の「契約」として操作f()の仕様を定義する、という考え方です。
操作に対する「契約」の内容を書き下すと次のようなものになります。
契約=『クライアント側が事前条件を満たした状態でf()を呼び出すことを約束してくれるのならば、サプライヤー側はf()を実行した結果として事後条件を満足することを保証します』
この記述から分かることは、f()の事前条件を満たすのは呼び出し側であるクライアントの責任である、ということです。事前条件が真の場合にだけ、サプライヤーであるf()の実装元は事後条件を真にして結果を返す責任を負います。
システムの振る舞いを正確に記述するには、契約に基づいてシステムの構成要素である各コンポーネント同士の振る舞いを仕様記述し、それらの合成としてシステム全体の振る舞いを定義するというコンポーネント指向の考え方に行き着きます。
この辺の具体的な手法に興味のある人は、「J. チーズマン・J. ダニエルズ著、『UMLコンポーネント設計』、ピアソンエデュケーション、2002年」をお勧めします。
では、本連載の「第4回 関連や関係の落とし穴(後編)」で出題した関連クラスの意味に関する問題の解答といきましょう。その前に簡単に関連クラスの復習をしておきます。
関連クラスというのは、オブジェクト間のリンク自体を実体として扱いましょうということですから、関連クラスのインスタンスは、リンクでもありオブジェクトでもある両義的な存在です。重要なことは、関連クラスも関連の性質(制約)を受け継ぐということです。ですから、関連クラスのインスタンスは両端のオブジェクトなしには存在できないオブジェクトです。
「関連クラスのインスタンスは両端のオブジェクトなしには存在できないオブジェクトである」と先ほどいいました。これはいい換えると、関連クラスのインスタンスは両端の参加クラスのインスタンスが特定されれば一意に定まる、ということを意味します。図書のインスタンス「砂の器393901」と利用者「田中退屈男041234」が特定されたとして、その両者の間の「貸出」クラスのインスタンスは一意に定まるでしょうか? 実は定まりません。同じ利用者が同じ本を時期を違えて借りることは許されているし実際にあり得るからです。つまり、田中退屈男さんは物理的にも同じ「砂の器」の本を2回、貸出0404010001と貸出0404210123と借りているかもしれないのです。
では、正しく適用された関連クラスの例を以下に示しておきます。
関連クラスを扱った「第3回 『関連』や『関係』の落とし穴 (前編)」において、関連クラスの誤解を明示するために、関連クラスと同等と思われる通常クラスとを比較して、「実は関連クラスと同じ意味をその通常クラスに持たせるには制約の追加が必要であるが,それをOCLで書き表しなさい」という問題を出していました。
関連クラスは普通のプログラミング言語には概念自体が存在しませんから、そのままでは実装できませんので、設計時に通常のクラスに置き換えます。以下のように置き換わることになります。しかしながら、このような通常クラスによる置換では、実は関連クラスで表わせていた意味(制約)のすべては表し切れていません。このクラス図だけだと、意図していないオブジェクト図も導出できてしまいます。さて、どのような制約をこのクラス図に追加すれば、元の関連クラスによるクラス図と同等の意味になるでしょうか。
これに対する答えは今回のいままでの解説をじっくり読んだ方は分かったのではないかと思いますが、念のため解答を示しておきます。
context 販売 inv: self.明細->isUnique(商品) context 商品 inv: self.明細->isUnique(販売)
つまり、販売オブジェクトの明細はすべて異なる商品オブジェクトに対応していなければならない(また同じように、商品(品目のこと)ごとの販売明細はすべて異なる販売に属さなければならない)、ということを表しています。この制約がないと、通常クラスとして表された「明細」のインスタンスは1個の商品インスタントとリンクを持つとしか規定されていないので、異なる2つの明細インスタンスから同一の商品インスタンスに対してリンクを張ることもできてしまうわけです。
この制約をisUnique()を使わずに次のようにOCLで書くこともできます。こちらの方が少し長くなりますが、いわんとするところが明快かもしれません。
context 販売 inv: self.明細->forAll( x,y|x<>y implies x.商品<>y.商品 ) context 商品 inv: self.明細->forAll( x,y|x<>y implies x.販売<>y.販売 )
いかがだったでしょうか。OCLの表現力に納得されましたか?
今回ご紹介した範囲では違いはありませんが、実は、OCLは現在OCL 2.0というバージョンが最新です。以前のバージョン1.4と見た目は大きく違いませんが、いくつかの点で拡張・改善されています。
Tuple {name: String = ‘John', age: Integer = 10} Tuple {a: Collection(Integer) = Set{1, 3, 4}, b: String = ‘foo', c: String = ‘bar'}
observer^update() bankAccount^getMoney(amount)
Copyright © ITmedia, Inc. All Rights Reserved.