「第7回 静的モデル:リレーションシップ(1)」、「第8回 静的モデル:リレーションシップ(2)」で、UMLのリレーションシップ(関係、relationship)について説明してきました。今回から、このリレーションシップの最も重要な具象モデルであるアソシエーション(関連、association)のJavaへのマッピングについて考えていきます。本連載では、UMLのメタモデルにおけるAssociationを以下の3つに分けています。
今回は、UMLアソシエーションとJavaへのマッピングの枠組みを確認することを目的に、最も意味の広いAssociationであるアソシエーションに焦点を絞って議論を進めることにします。なお、本連載ではUMLのメタモデルの議論っぽく、リレーションシップをRelationship、アソシエーションをAssociationと記述することにしています。そのほかのリレーションシップもメタモデルの定義どおり英語の用語を用います。
前回、UMLのメタモデルにおけるAssociationの意味を以下のようにご紹介しました。
Associationは、分類子のインスタンス間にコネクションがあることを示すRelationshipです。
この意味を具体的に表したものが図1です。図1の上側はクラス図におけるクラスとアソシエーションです。
図1の下側がクラスとアソシエーションをインスタンス化(実体化)した結果です。このクラスをインスタンス化するとオブジェクトに、アソシエーションをインスタンス化するとリンクになります。このオブジェクトとリンクはオブジェクト図やコラボレーション図の重要な構成要素となります。
オブジェクト指向における動的モデルは、オブジェクト間のメッセージ送受信が核となります。このメッセージ送受信がオブジェクト間で可能となるためには、オブジェクト間にリンクが張られている必要があります。このリンクはパイプをイメージするとよいでしょう。パイプの中をメッセージが流れるイメージです。つまり、オブジェクト指向の理論上はアソシエーションが存在するクラスのインスタンスオブジェクト間でのみメッセージの送受信が行え、そしてその実現手段の1つであるオペレーション(メソッド)の呼び出しが行えることになるわけです。
アソシエーションは非常に多彩な修飾方法を持っています。まずUMLにおけるアソシエーションについて調べてみましょう。
アソシエーションに対して行うことができる修飾を理解するためには、アソシエーションのメタモデルについて理解することが有効です。図2は、UMLのアソシエーションに関連するメタモデルです(分かりやすいように簡略化しています)。ノーテーション的には、図中に示したようにアソシエーション、アソシエーションエンド、アソシエーションエンドに限定子というロールで接続された属性の塊をアソシエーションと呼んでいます。
この図から、UMLのアソシエーションがどのようなモデル要素であるかが分かります(分類子はクラスと読み替えた方が理解しやすいかもしれません)。分類子とアソシエーションは直接接続されておらずアソシエーションエンドというモデル要素が間に挿入されています。つまり、アソシエーションが接続されている分類子の数だけアソシエーションエンドが存在することになります。アソシエーションエンドは分類子とアソシエーションを接続するためのジョイントとなるわけです。
アソシエーションそのものには、属性としてnameしか定義されていません。それに対して、アソシエーションエンドには以下に示す多数の属性が定義されています。
つまりアソシエーションと分類子の接点の部分で、多彩な修飾を行うことができるわけです。アソシエーションにおける表現力はアソシエーションエンドによってもたらされているといえます。これらの属性の意味と用法は次回以降に取り上げる予定です。
また、このメタモデルのクラス図から以下のことが分かります。
前節ではアソシエーションに関するメタモデルについて簡単に説明しました。それでは、このメタモデルはグラフィカル言語としてのノーテーションではどのように表現されるでしょうか。
前節で述べたように、ノーテーションという観点からは、メタモデルにおけるアソシエーションとアソシエーションエンド、アソシエーションエンドから参照される限定子のための属性までを総称してアソシエーションと呼びます。最も簡単な形のアソシエーションは1本の線で表現されますが、実際のメタモデルは以上のように、かなり複雑なモデルとなっています。
図3は、メタモデルのインスタンスであるモデルです。この図に示すように分類子と分類子の間に、「アソシエーションエンド」、「アソシエーション」、「アソシエーションエンド」という順番で各モデル要素が配置されます。
通常、1つのアソシエーションに接続される分類子は2つであることが多く、この種類のアソシエーションを特に「バイナリアソシエーション(Binary Association)」と呼びます。 まれに2つ以上の分類子に接続されるアソシエーションが使用されることもあります。このようなアソシエーションを「N項アソシエーション(N-Ary Association)」と呼びます。図4は、アソシエーションに関連付けられている分類子の数が3つの場合です。この場合は3項アソシエーションとなります。
アソシエーションそのものは線によるパスによって表現されますが、そのパス上にさまざまな形の修飾を行うことができます。
アソシエーションエンドに限定子というロールを持った属性が接続されている場合は、図5となります。これは、限定子を用いたアソシエーションとなります。
さらに、クラスとアソシエーションの両方の性質を併せ持ったアソシエーションクラスというモデル要素もあります。アソシエーションクラスを用いた図は図6です。クラスとアソシエーションの性質を併(あわ)せ持つということはどういうことなのか、具体的にはどのような用途で使うのか、といった点もイメージがわきにくいところです。
以上のように、UMLのアソシエーションは多彩な表現能力を持っています。この中で、どの表現形式がJavaとのマッピングで利用されるのか、その場合にはJavaでどのように実装することになるのか、といった点を明らかにしないと現実の開発には利用できないことになります。
前節では、UMLのアソシエーションについて説明しました。一口にアソシエーションといっても、いろいろな種類のアソシエーションがあります。このためJavaへのマッピングも一筋縄ではいきません。さらに問題となるのは、Javaでは、アソシエーションに直接対応する部品が用意されていない点です。このため、「変数」や「クラス」を用いてアソシエーションを実現するというアプローチになります。本節では、Javaの立場からアソシエーションを実現するための部品について整理します。
「第3回 静的モデル:UMLとJavaのクラスをメタモデルから把握する」で説明したとおり、Javaには以下に示す種類の変数があります。
インスタンス変数は、オブジェクトの生存中に有効となる変数です。別のいい方をすると、インスタンス変数のライフサイクルはインスタンスオブジェクトのライフサイクルと同一のものになります。
インスタンス変数の例を以下に示します。クラスAccountでインスタンス変数nameが宣言されています。このインスタンス変数nameは、クラスAccountのインスタンスオブジェクトの生成と同時に有効となり、消滅と同時に無効となります。
public class Account { private String name; }
クラス変数は、クラスの生存中に有効となる変数です。修飾子staticを指定します。クラス変数の例を以下に示します。クラスAccountで、クラス変数numberOfAccountsが宣言されています。
public class Account { private static int numberOfAccounts; }
ローカル変数は、メソッド内で宣言される変数です。変数が宣言されている場所から、例外が宣言されているブロックの終了場所までが有効となります。ローカル変数の例を以下に示します。クラスAccount内で定義されているメソッドgetNameにおいてローカル変数nameが宣言されています。
public class Account { public int getName() { String name; ... } }
コンストラクタパラメータは、コンストラクタの引数として宣言された変数です。コンストラクタの開始から終了時まで有効となります。コンストラクタパラメータの例を以下に示します。クラスAccount内で定義されているコンストラクタにおいて、引数としてパラメータnameが宣言されています。
public class Account { public Account(String name) { ... } }
メソッドパラメータは、メソッドの引数として宣言された変数です。メソッドの開始から終了時まで有効となります。メソッドパラメータの例を以下に示します。クラスAccount内で定義されているメソッドsetBalanceにおいて、引数としてパラメータbalanceが宣言されています。
public class Account { public void setBalance(long balance) { ... } }
例外ハンドリングパラメタは、catch節において例外を受け取るための変数です。catch節の開始から終了時まで有効となります。例外ハンドリングパラメタの例を以下に示します。catch節において例外IOExceptionを受け取るための例外ハンドリングパラメタeが宣言されています。
try { .. } catch (IOException e) { e.printStackTrace(); }
以上、Javaの変数を一通り確認しました。
アソシエーションは、オブジェクトとオブジェクトの間に何らかのリンクが張られていることを示したモデル要素ですから、これらの変数はすべて何らかのアソシエーションとしてモデル化することが可能となります。しかし、現実の開発作業の中で使用するクラス図においてローカル変数や例外ハンドリングパラメータまですべてアソシエーションとして表現していては、非常に煩雑なクラス図となってしまうことは明らかです。
また、上流工程において作成されたクラス図で定義されているアソシエーションをJavaクラスに落とし込む際にも、ローカル変数や例外ハンドリングパラメータを利用する必然性はあまりありません。
以上の点から、Java変数とアソシエーションの対応を考える場合も、実用という観点からターゲットを絞り込む必要があります。
Javaにおけるアソシエーションの実現方法あるいはUMLアソシエーションとして表現されるべきJavaの部品としては、変数以外にクラスも考えられます。
配列はJavaにおいて最も重要なコンテナオブジェクトです。配列はJava言語仕様およびJava VMによって特別に意識されているオブジェクトであり、Javaプログラミングにおいて極めて重要な以下の性質を持っています。
配列は、Java VMが直接意識しているコンテナオブジェクトなので、極めて高速に動作します。配列以外のすべてのコンテナオブジェクトは、内部的に配列を使用することになるため、構造上配列よりも低速になってしまうのです。配列のもう1つの特徴は、型を指定できる点です。配列以外のコンテナオブジェクトはjava.lang.Objectをターゲットにしているため、静的モデルを基本とするJavaのプログラミングスタイルとは若干の不整合が発生します。強い型付けによるプログラム品質の確保という観点からは、コンテナオブジェクトよりも配列を利用する方が望ましいわけです(注1)。
配列と並んで重要な部品はコレクションライブラリと呼ばれるクラス群です。前述したように配列は極めて重要な部品ですが、利用できる機能が限られるため利用できる用途も限られてしまいます。つまり、実用上はコレクションライブラリの利用が重要となってきます。コレクションライブラリでは、以下の3つのJavaインターフェイスを軸に、これらのインターフェイスを実装したさまざまな具象クラスが用意されています。
・java.util.List:順序を持った要素の集まり:順序を持った要素の集まり
・java.util.Set:重複を持たない要素の集まり:重複を持たない要素の集まり
・java.util.Map:キーに対応する要素の集まり:キーに対応する要素の集まり
コレクションライブラリのインターフェイスと具象クラスの対応を表1に示します。
インターフェイス | 具象クラス | 備考 |
List | ArrayList | |
LinkedList | ||
Vector | JDK 1.1互換 | |
Set | HashSet | |
TreeSet | ||
Map | HashMap | |
TreeMap | ||
Hashtable | JDK 1.1互換 | |
WeakHashMap | J2SE 1.3より | |
IdentityHashMap | J2SE 1.4より | |
表1 Javaのコレクションライブラリ |
コレクションライブラリではJavaインターフェイスjava.util.Listの具象クラスとしてjava.util.ArrayListとjava.util.LinkedListを用意しています。
java.util.ArrayListとjava.util.LinkedListは、振る舞いは同じですが、動作性能や使用する資源の量が異なっています。UMLのアソシエーションとJavaのコレクションライブラリのマッピングを考える場合、Javaのコレクションライブラリの以下の点が論点となってきます。
・インターフェイスによって定められた機能
・具象クラスの選択
ここまでで説明したJavaの部品である変数、配列、コレクションライブラリを用いればほとんどのUMLアソシエーションを実装できると考えられますが、場合によっては専用のクラスを作成する必要が出てくるかもしれません。少なくともUMLのアソシエーションクラスは、専用クラスを作成する必要がありそうです。
UMLのクラスとJavaのクラスのマッピングも、細かく考えていくとなかなか大変でした。しかし、クラスはUMLとJavaにおいて多少の機能差があるとはいえ、同じ部品が用意されており、そういう意味では考えやすいテーマだったといえます。
それに対して、アソシエーションはUMLのみにあるモデル要素であり、Java側には直接対応する部品はありません。このため、クラスのマッピングと比べてさらに難易度の高い考慮が必要となります。この点について図7のUMLモデルを例に考えてみましょう。
UMLアソシエーションをJavaの世界に持ってくる方法として、一番手堅いのは各メタモデルのインスタンスをクラスとして実現する方法です。
図7のアソシエーションの場合、クラスPerson(リスト7)とクラスCompany(リスト8)の間に存在するアソシエーションを、アソシエーション本体を実現するクラスEmploy(リスト9)およびアソシエーションエンドを実現する2つのクラスEmployee(リスト10)、Anon(リスト11)によって構築することができます。そして、これらのクラスをインスタンス変数を用いて接続していくわけです。
public class Person { public Employee employ; }
public class Company { public CompanyEmployAssociationEnd employ; }
public class EmployAssociation { public String name = null; public AssociationEnd left; public AssociationEnd right; }
public class Employee { public Person classifier; public Employ association; }
public class Anon { public Company classifier; public Employ association; }
理屈としては、この方法でアソシエーションを実現することは可能ですが、得られる効果に対してあまりにも煩雑な構造であることはいうまでもありません。
それでは、Javaにおける図7のアソシエーションの妥当な実現方法はどのようなものでしょうか。
この場合、アソシエーションが示す意味を実現するための最小限の機能が得られればよいと考えます。つまり、最小限の機能を満たせる、できるだけシンプルな構造を考えるというアプローチが有効です。図7のアソシエーションは、以下の意味を持っています。
つまり、JavaクラスCompanyが一方的にJavaクラスPersonを参照できれば、アソシエーションの持つ機能を満たすことができます。この条件を満たす最も簡単な方法は、JavaクラスCompanyのインスタンス変数にJavaクラスPersonへの参照を保持することです。このとき、変数名として名前が必要となるので、アソシエーションにおけるクラスPersonのロール名employeeを使用してみましょう。
この結果、リスト12に示すJavaクラスPersonとリスト13に示すJavaクラスCompanyによってアソシエーションを実現することができました。
public class Person { }
public class Company { public Person employee; }
例題のアソシエーションの場合、実用的な見地からは、このマッピングが適切であるのは異論がないでしょう。
前節では、アソシエーションのマッピングについて考えてみました。UMLの観点からは、UMLにしかないモデル要素であるアソシエーションをJavaの変数とクラスを用いてどのように実現するのかという点が論点となります。例えば、アソシエーションを配列あるいはコレクションライブラリで実現することになるのか、あるいは新規にクラスを作成しなければならないのか、といったことが論点となってきます。それに対してJavaの観点からは、Javaの持つ機能である配列やコレクションライブラリをどのようにしてUMLで表現するのか、という点が論点となってきます。
つまり、同じオブジェクト指向の技術とはいえ図8に示すようにUMLとJavaには明らかな機能差があるため、そのマッピングを考えるのは思ったより大変な作業となるわけです。JavaをUMLにマッピング、あるいはUMLをJavaにマッピングといった単一方向のマッピングのアプローチだけでは、うまくマッピングを定義することはできません。この点にUMLとJavaのマッピングを考えるうえでの難しさがあります。
この問題を解決するためには、UMLとJavaの機能差を見極めたうえで、図9に示すようにUMLとJavaを包含するプロファイルを定義し、このプロファイルにおけるUMLやJavaでの実現方法を整理する必要があります。本連載の目的はJavaをターゲットにした設計モデルですから、以下の点を満たすプロファイルを定義することになります。
UMLとJavaは同じオブジェクト指向のカテゴリにある技術ですが、オブジェクト指向モデルとオブジェクト指向プログラミングの微妙な機能差を反映する形で、具体的な部品レベルでは細かな相違が多数あります。この細かな相違がUMLとJavaのマッピングを考えるうえでのハードルとなるわけですが、今回検討したアソシエーションはそのハードルの1つということができます。
このハードルをクリアするための準備として、今回はアソシエーションのマッピングを考えるうえでの枠組み、UMLアソシエーションとJavaのマッピングに関する基本構造について整理しました。次回から、UMLアソシエーションとJavaの具体的なマッピングについて検討していきます。
Copyright © ITmedia, Inc. All Rights Reserved.