連載第2回のテーマは「モデルの中に隠された気付きにくい曖昧さ」です。

UMLでモデルを記述する理由のひとつとして、「システムの仕様情報などを、早く、安く(低コストで)、かつ、正確に伝達したい」という点があげられると思います。が、UMLも所詮は言語(ある概念を表現する表記形式)の一種でしかないので、表現の「正確さ」はその言語を用いる(読み書きする)人の使い方によって大きく左右されることがあります。同じモデル図を見ても、それを読む人によっては自分(描き手)が頭の中で思い描いている構造とはまったく違った構造として解釈されることがあります。もちろん、人それぞれの背景知識や理解の仕方が異なるから...という理由もあるのでしょうが、モデルに含まれている「曖昧さ」に気付かず放置されてしまっている事に起因する場合も少なくないと思います。

今回は、モデル図の中に知らず知らずのうちに含まれてしまっている気付きにくい「曖昧さ」の例と、それらの曖昧さを排除するための表記例をいくつか紹介していきたいと思います。

その1: 排他的な関連

前回の記事「第1回: コンポジションにまつわるアレコレ」の例として 図1 のようなクラス図が出てきました。

図1:「バイク」、「自動車」、「タイヤ」の関係(その1)
図1:「バイク」、「自動車」、「タイヤ」の関係(その1)

この例では、コンポジションの共有不可制約のため「タイヤ」側から見た「バイク」および「自動車」側の多重度は「0..1」にならなくてはならないのですが、その都合で『「タイヤ」のインスタンスは「バイク」や「自動車」のインスタンスに保有されていなくても(単独で)存在することもできる』という構造になっています。でも、『「タイヤ」のインスタンスがポッとひとつ浮いて存在してしまうような構造は許したくない(「タイヤ」は「バイク」か「自動車」のどちらかに必ず保有されていて欲しい)』という場合はどう表現したら良いでしょうか?

すぐに思い付く方法は、UMLでもっとも強力な汎用モデル要素であるコメントを使う...という手でしょうか(図2)。

図2:コメントで注記した例
図2:コメントで注記した例

もう少しフォーマルな感じで、コメントの部分をOCL(※1)形式で記述してみると、たとえば 図3 のようになります。

図3:OCLで制約式を表記した例 (その1)
図3:OCLで制約式を表記した例 (その1)

図3 の制約式「 { ( self.バイク->notEmpty() ) or ( self.自動車->notEmpty() ) } 」を日本語的に記述してみると、『自分(この場合は1つの「タイヤ」のインスタンス)から辿れる「バイク」が空でない(相手のインスタンスが存在する)、または、自分から辿れる「自動車」が空でない』といった感じの内容になるでしょう。この条件式だとorの左辺と右辺の両方がtrueになっても良さそうで少し混乱してしまうかもしれません(実際はコンポジションの共有不可制約があるので、左辺と右辺の両方が同時にtrueになることは許されないので問題ありませんが...)。別の記述方法としては 図4 のようなものも考えられます。

図4:OCLで制約式を表記した例 (その2)
図4:OCLで制約式を表記した例 (その2)

図4 の制約式中で用いられているxor演算子(exclusive-or: 排他論理和)は、左辺と右辺の条件式が両方ともtrueまたは両方ともfalseだった時にfalseになる二項論理演算子です(つまり、この制約式は左辺か右辺のどちらか片方だけがtrueでもう一方がfalseの時だけtrueになります)。

図5:関連間の { xor } 制約を使って表記した例
図5:関連間の { xor } 制約を使って表記した例

図5 は関連間の{xor}制約を使って表記した例です。この例の場合、あるひとつの「タイヤ」のインスタンスから見て、常に{xor}制約で繋がれている2つの関連線のうちのどちらか片方だけが有効になる...というふうに解釈します。ただし、関連間の{xor}制約は解釈が曖昧になってしまうケースもあるのであまり使わない方が良いのかもしれません。

最後に、『「バイク」と「自動車」で共通な部分が多く、抽象クラスとしてまとめることができる』という場合、図6 のような表記も考えられそうです。

図6:抽象クラスを導入して関連線をまとめた例
図6:抽象クラスを導入して関連線をまとめた例

図6 の例の場合、「車体」という抽象クラスを導入することによって、「バイク」と「タイヤ」の間、および、「自動車」と「タイヤ」の間に引かれていた二本の別々の関連線を、「車体」と「タイヤ」の間に引かれる一本の関連線にまとめています。その結果、「タイヤ」側から見た「車体」の多重度は「1」になるので、これによって「タイヤ」のインスタンスが単独で存在することはなくなります。また、「バイク」や「自動車」と並ぶ位置に他の種類のサブクラス(たとえば「三輪車」など)が追加されるような場合にも関連線が増えないで済むので有利な構造です。反面、「車体」側から見た「タイヤ」の多重度は「2..4」と幅を持たせた形になっており、この部分は少し曖昧さが大きくなってしまっていると言えるでしょう。この曖昧さを排除するために、図6 のクラス図では「バイク」と「自動車」双方に簡単な制約式を添えて補強しています。

※1: OCL: Object Constraint Language, オブジェクト制約言語
OCLはUMLと一緒にOMGによって仕様定義されている制約記述のためのテキスト形式の言語構文です。UML仕様としてはUML図中に記述される制約式の書式はツール/環境依存で、どんな記述形式を採用しても良いということになっています(たとえば、JavaやC++などのプログラミング言語の構文で記述しても良い)が、OCLの構文はUML仕様と親和性が高いのでモデル中の制約式の内容を記述するための言語として標準的によく使用されています。

その2: 三つ巴の関連

図7 のクラス図を見てみてください。

図7:三つ巴の関連(クラス図)
図7:三つ巴の関連(クラス図)

このクラス図に合致するオブジェクト図として普通にぱっと思いつくのは 図8 のような構造ですが、実は良く考えてみると 図9 のような構造でもこのクラス図に合致します。

図8:オブジェクト図の例 (その1)
図8:オブジェクト図の例 (その1)

図9:オブジェクト図の例 (その2)
図9:オブジェクト図の例 (その2)

オブジェクトの構造を 図8 のような構造のみに限定したい場合、元の 図7 クラス図に制約を追加するなどして補強する必要があります(図10)。

図10:制約式で補強したクラス図の例
図10:制約式で補強したクラス図の例)

このクラス図に記述されている制約式「 { self.勝ち.勝ち = self.負け } 」を日本語的に記述してみると、『自分(この場合は「ちょき」のあるインスタンス)から「勝ち」という関連端名で辿れるインスタンス(これは「ぐ~」クラスのあるインスタンスになる)から、さらに「勝ち」という関連端名で辿れるインスタンス(これは「ぱ~」クラスのあるインスタンスになる)は、自分から「負け」という関連端名で辿れるインスタンスと同じものでなければならない』といった感じになります。もちろん、この制約式は「ぐ~」や「ぱ~」を起点(self)として記述してもOKです。

この例のように、3つ以上のクラス間に役割が同様な関連線が数本引かれている場合、人間はそれらの関連線同士が何らかの相関関係を持っているかのように思い込んで解釈しまう場合があります。が、たとえ同じような関連端名が付けられ、似たような多重度が振られていても、クラス図上で関連線として別々に引かれているものであれば基本的にそれらの関連同士はまったく独立した別々のものとして解釈するように意識する必要があります。そうでないと、読む人の思い込みに頼った曖昧なモデルを描いてしまったり、あるクラス図に合致するオブジェクト群の構造バリエーションの可能性を広く想像することができなくなってしまうかもしれません。

その3: 関連間の制約

図11 のクラス図は「町内会」と「世帯主」を含む問題領域のモデルの一部です(説明のため2本の関連線を青と赤で色分けして表示しています)。

図11:二つのクラス間に引かれた二つの関連線
図11:二つのクラス間に引かれた二つの関連線

クラスが2つで関連線も2本しかない非常にシンプルなモデルなので特に問題なさそうに思えますが、このクラス図だと 図12 のようなオブジェクト構造も許す形になっています。

図12:制約が無い場合にとり得るオブジェクト構造の例
図12:制約が無い場合にとり得るオブジェクト構造の例

図12 では、「稲荷町:町内会」の会長が「稲荷町:町内会」の会員ではない「田中さん:世帯主」になっています(「田中さん:世帯主」は「熊野町:町内会」の会員)。私達(日本人)は世間一般の常識から無意識のうちに「ある町内会の会員でない人がその町内会の会長役を勤めることはないだろう」という思い込みをしてしまいがちです。そのため、図12 に示したようなインスタンス群の構造もあり得るということ自体、なかなか思いつきにくかったりします(常識が邪魔をする)。

それでは、「ある町内会の会長はその町内会の会員の中の誰かでなければならない」という制約をクラス図の中に明記するにはどうすれば良いでしょうか? この例のように「ある関連で参照されるインスタンス群が同じインスタンスから他の関連で参照されるインスタンス群の部分集合でなければならない」というケースは意外に多いので、UMLでは関連端に付けられる subsets という制約があらかじめ用意されています(UML仕様の定義上、厳密にはsubsetsは制約式ではなく関連端のプロパティとして扱われます)。subsetsを使って制約を補強したクラス図を 図13 に示します。

図13:subsets制約を追加して補強したクラス図
図13:subsets制約を追加して補強したクラス図

図13 の「会長」側の関連端に追加された「 { subsets 会員 } 」という記述によって、『ある「町内会」のインスタンスから「会長」として参照される「世帯主」のインスタンスは、同じ「町内会」のインスタンスから「会員」として参照されている「世帯主」のインスタンス群の中のいずれかでなければならない』という事が示されています。

subsetsと組合せで使われるもので {union} という表記があります。{union}の使用例を 図14 に示します。

図14:{union}の使用例
図14:{union}の使用例

{union}は派生関連端(何か別の情報によって参照先のインスタンス群が派生的に決定される)に付けられるプロパティで、その関連端をsubsetsで参照している他の関連端から辿れるすべてのインスタンス群の和集合となることを示します。たとえば、図14 の例の場合、ある「団体」は「男性会員」と「女性会員」を区別して会員登録しています。ある「団体」から関連端名「会員」で辿れる「人」の集合は、その「団体」の「男性会員」の集合と「女性会員」の集合の和集合となります。このように、複数の他の関連の合成として関連を構成したりしたい場合にunionとsubsetsを組み合わせて使用すると便利です。

おわりに

自然言語や表形式での仕様記述に比べると、UMLのクラス図は理解しやすくある程度厳密な記述を行うことができます。が、より細かく突き詰めて考えてみると、コメントや制約式などの補助的なモデル要素で補強しないと曖昧になってしまう(誤解を招いてしまいかねない)部分も意外と多いものです。そういった曖昧さをできるだけ排除していくためには、まず「ここが曖昧になっている」と気付けることが必要で、次にそれらの曖昧さを効果的に解消できるような表記法を覚えて正確に使いこなせるように訓練していくのが良いでしょう。