前回(第3回 静的モデル:クラスにおけるUMLとJavaのマッピング(1))は、UMLの“クラス”とJavaの“クラス”についてそれぞれを構成する部品をメタモデルという形で明確化しました。今回は、このメタモデルを比較することによって、UMLとJavaのマッピングを考えていきます。
設計モデルは、プログラミング言語やミドルウェアといった実行環境上における実装を意識したモデルです。本連載は、Javaによる実装をターゲットにしている設計モデルとJavaのマッピングをテーマにしています。このため、UMLを使ってどのようにしてJavaの部品を表現するのか、という点がマッピングを考えるうえでの最重要課題となります。
加えて、オブジェクト指向設計上重要でありながらもJava言語では直接サポートしていないモデル要素についてのJava側での対応も考えていきます。
今回は、以下の戦略によりマッピングを考えていきます。
まず、UMLクラスとJavaクラスのマッピングについて考えてみましょう。なお、UMLシグナルとUML例外は基本構造がほとんどUMLクラスと同じであり、Java側ではクラスとして実装されるので、クラスのマッピングに含めて考えることにします。
UMLクラスの部品とJavaクラスの部品のマッピングは図1となります。当然ですが、UMLのクラスとJavaのクラスではかなりの部分が共通となっています。
UMLのクラス名はJavaでもクラス名となります。UMLの可視性はJavaでも可視性として表現されます。
UMLの属性はJavaではインスタンス変数またはクラス変数となります。インスタンススコープを持つUML属性がJavaではインスタンス変数として、クラススコープを持つUML属性がJavaではクラス変数として実装されます。
UMLにおけるオペレーションは、Javaのメソッド、クラスメソッド、コンストラクタに対応します。
抽象クラスの宣言はUMLとJavaの両方に用意されています。UMLではプロパティにabstractを指定、Javaではクラス宣言で修飾子abstractを指定することで抽象クラスを宣言したことになります。
サブクラスを持たないクラスの宣言もUMLとJavaの両方に用意されています。UMLではプロパティにleafを指定、Javaではクラス宣言で修飾子finalを指定することでサブクラスを持たないクラスを宣言したことになります。
activeは、アクティブオブジェクトを示すプロパティです。Javaでは、java.lang.Threadを用いて実装することになるでしょう。
rootは、ルートのクラスであることを示すプロパティです。Javaでは、クラスとしてrootプロパティが対応するのはjava.lang.Objectになります。
定数はJavaのみに存在する部品です。定数をUMLで表現するためには以下の2つの手法が考えられます。
属性を利用する場合には、専用のステレオタイプを作る必要があります。定数宣言専用の並び区画を設け、この中にJavaの文法あるいはUMLの文法に準じる形で記述する方法も有力です。
persistenceは、オブジェクトの永続性を示すプロパティです。Javaのオブジェクトは永続性を持っていないので、persistenceに対応するJavaの部品はありません。
UMLインターフェイスの部品とJavaインターフェイスの部品のマッピングは図2となります。
まず、共通しているのがインターフェイス名です。可視性も共通しています。UMLのオペレーションはJavaではメソッドに対応します。
UMLインターフェイスとJavaインターフェイスの最大の相違点は、Javaインターフェイスが定数を宣言できる点です。UMLで定数を表現するには以下の2つの方法が考えられます。
これらの実現方法はクラスの場合と同様です。
インターフェイスという意味で注意しなければならないのは、UMLのインターフェイスでは属性を定義することができないという点です。この問題を満足させる方法という点においては、定数宣言専用の並び区画を用いる方法が有力です。
Javaでは、インターフェイスの修飾子としてstrictfpが指定できます。このstrictfpに相当する機能はUMLにはありません。UMLではプロパティで表現するのが適切でしょう。
プロパティrootとプロパティleafは、UMLのみに定義された部品ということになります。無理をしてJavaで実装する必要もないので、設計上の補助情報として用いるのがよいでしょう。
まずUMLの“クラス”とJavaの“クラス”を列挙し、それぞれの“クラス”の対応関係を見ていきます。UMLの“クラス”は以下のものに分類できます。
以上のUMLの“クラス”が、Javaの何にマップできるのかという観点で調べます。Javaのクラスは以下のものに分類できます。
また、Javaのインターフェイスは以下のものに分類できます。
部品が出そろったところで、具体的な検討に入ることにしましょう。まず、UMLの“クラス”を起点にJavaのクラスとインターフェイスとのマッピングについて見ていきます。マッピングの結果は図3となります。
UMLにおける「普通のクラス」はJavaでもそのまま「普通のクラス」にマッピングされます。
UMLにおいて「ステレオタイプmetaClassのクラス」は、Javaではjava.lang.Classに相当します。
UMLの「ステレオタイプthreadのクラス」は、軽量プロセス(いわゆるスレッド)を持ったアクティブオブジェクトの実装を意味します。これは、Javaのjava.lang.Threadに相当すると考えてよいでしょう。
UMLの「ステレオタイプutilityのクラス」、すなわちユーティリティはJavaに直接対応する機能はありませんが、Javaで実装することは可能です。
Javaでは、クラススコープを持ったメソッドであるクラスメソッドを定義することができます。このクラスメソッドのみを定義したクラスがユーティリティに対応します。このようなユーティリティはJavaにおいても頻出のテクニックです。例えば数値演算の関数をまとめたjava.lang.Mathがこのユーティリティに相当します。
UMLの「インターフェイス」は、Javaの「普通のインターフェイス」に相当します。ただし、Javaのインターフェイスでは定数の定義ができる点には注意が必要です。
シグナルはオブジェクト間でやりとりされる非同期の「刺激(stimulus)」を表現する分類子です。
Javaの言語では、java.lang.Throwableがこのシグナルに相当します。java.lang.Throwableは、throw句で送出され、catch句で受信することで、通常の制御フローを飛び越えて、直接制御を移すことができます。
例外はシグナルの一種で、異常状態を通知するために用いられる分類子です。Javaの言語仕様上一番ぴったりくるのが java.lang.Exceptionです。もちろんjava.lang.Exceptionのサブクラスであるjava.lang.RuntimeExceptionも例外としてモデル化されることになります。単なるjava.lang.Exceptionとjava.lang.RuntimeExceptionでは、重大な機能上の相違があります。しかし、UMLの例外ではこの機能上の相違を十分に表現することができません。この点をどのように実現するかがマッピングにおける検討項目になってきます。
この実現には以下の2つの方法が考えられます。
java.lang.Errorは、java.lang.Throwableのサブクラスなので、少なくともシグナルとなるのは確かです。そこで論点となるのは java.lang.Errorが単なるシグナルではなくてUML例外に相当するか否かという点になります。java.lang.Errorは、java.lang.Exception と機能は異なりますが、異常状態を示すオブジェクトであることには変わりないのでUML的には「例外」と考えるのが無難です。
java.lang.ErrorをUML例外と考えるとステレオタイプexceptionを用いるだけではjava.lang.Exception(java.lang.RuntimeException)のサブクラスである例外なのか、java.lang.Errorのサブクラスである例外なのかの判断がつきにくくなります。この点の区別を明確にするためには、汎化を用いて例外とjava.lang.Errorやjava.lang.Exceptionの関係を明確にする方法が普通です。しかし例外が登場するたびに汎化による指定を毎回行っていては煩雑で仕方がありませんし、クラス図も見づらいものになってしまいます。書きやすさや図の見やすさを考慮するのであれば専用の拡張プロパティを導入する方法が有力です(図4)。
プロパティなし | java.lang.Exceptionの子孫 |
---|---|
プロパティruntime | java.lang.RuntimeExceptionの子孫 |
プロパティerror | java.lang.Errorの子孫 |
以上の考察から、残された“クラス”は、UMLに直接対応するモデル要素がないもの、ということになります。このカテゴリの“クラス”としては、java.lang.Object、java.lang.Stringの2つのクラス、java.lang.Cloneable、java.lang.Runnable、java.io.Serializable、java.io.Externalizableの4つのインターフェイスが相当します。
これらの“クラス”に関しては、無理やりマッピングを考えなくても、通常のクラスやインターフェイスと同様の方法を用いることでマッピングを行うことができます。しかしもっと便利な方法があるかもしれないので、ここではもう少し踏み込んで考えてみましょう。
java.lang.Objectは、すべてのJavaオブジェクトのルートとなるオブジェクトです。Javaの文法上は、extendsによる親クラスの指定を行わないクラスの定義が可能ですが、この場合には暗黙的にjava.lang.Objectが親クラスとして使用されます。
ユーザー定義クラスとjava.lang.Objectを表現する方法としては図5に示す方法が考えられます。
(1)はJavaの文法どおり、ユーザー定義クラスとjava.lang.Objectの関係を明示しない方法です。
(2)は、“Object”というクラスを継承する方法です。Objectがjava.lang.Objectと同一のものであるという暗黙の関係を想定しています。
(3)は、java.lang.Objectのパッケージ名を明記した形でユーザー定義クラスとの継承関係を表現する方法です。
(4)は、(3)と同じ意味ですが、パッケージアイコンを利用して、java.lang.Objectのパッケージを表現しています。Javaによる実装モデルをオブジェクトモデルに正確にマッピングすると(3)または(4)となります。しかし、いずれの方法も実務で使用するには煩雑です。
そこで、実用上は(1)の方法を取るのが普通です。つまり、Java文法の暗黙の設定をオブジェクトモデルにそのまま反映させてしまうわけです。
(2)は、(3)や(4)と同じ方法ですが、パッケージを明記していない点が異なります。Javaでは、パッケージjava.langに所属するクラスは、パッケージ名を指定しなくてもそのまま利用できる仕様になっています。この仕様を反映する形で、パッケージ名を省略した“Object”を参照するという方法も選択肢として考えられます。
Java言語で文字列を表すjava.lang.Stringは、オブジェクトではありますが、利用のされ方という点ではデータ型的な含みもあります。
一方、UMLではStringというデータ型を用意しており、このUMLのStringとJavaのjava.lang.Stringのマッピングは少し検討が必要となってきます。
取りあえず、今回は「java.lang.StringはUMLのデータ型Stringにマッピングされる」ものと考えておくことにします。この点の考察は次回以降「データ型」のマッピングの検討の中で行う予定です。
Java言語の文法はC言語の文法を踏襲しているため、Javaの配列もCの配列と同じものと錯覚しがちです。しかし、実際のJavaの配列は、内部の実装は特殊なオブジェクトとなっています。つまり、Javaの変数はオブジェクトへの参照を保持しており、この参照先がたまたま配列オブジェクトだった場合は、配列を操作することになるわけです。もちろんこれは、このような錯覚を起こすようにJava言語がシンタックスシュガーをまぶしているからです。
UMLの属性は多重性や順序性というモデル要素を持っていますが、これらのモデル要素のJavaへのマッピングを考える場合には、配列というオブジェクトへのマッピングを検討しなければなりません。また、java.util.Listなどのコレクションライブラリも検討対象となってきます。 以上の点から、属性の多重性および順序性については、次回以降「アソシエーション」の検討の中で併せて行いたいと思います。
ユーザー定義クラスとjava.lang.Cloneableの関係の表現には図6に示す方法が考えられます。パッケージ名の扱い方については、java.lang.Objectで検討したクラスの場合と同様の論点があります。
一般のインターフェイスの場合には、(1)か(2)を取ります。
java.lang.Cloneableのように、実装モデル上特別な意味を持つインターフェイスについては、(3)のプロパティや、(4)のステレオタイプといったUML拡張メカニズムを用いて表現する方法も考えられます。拡張メカニズムを用いる場合には、(3)に示すプロパティを用いる方が適切と考えられます。
ユーザー定義クラスとjava.lang.Runnableの関係の表現もjava.lang.Cloneableと同様に以下の選択肢があります。
java.io.Serializableは、UMLのクラスで利用されるプロパティpersistenceとは意味が異なり、直列化可能という性質を表します。
ユーザー定義クラスとjava.io.Serializableの関係の表現もjava.lang.Cloneableと同様に以下の選択肢があります。
java.io.Externalizableは、java.io.Serializableのサブインターフェイスであり、java.io.Serializableと同様にUMLのクラスで利
用されるプロパティpersistenceとは意味が異なります。
ユーザー定義クラスとjava.io.Serializableの関係の表現もjava.lang.Cloneableと同様に以下の選択肢があります。
Copyright © ITmedia, Inc. All Rights Reserved.