前節までで、UML“クラス”とJava“クラス”のマッピングを考えるための情報を整理しました。
簡単なクラス図を書くことが目的であれば、UML“クラス”とJava“クラス”はほとんどそのままマッピングできます。しかし、CASEツールを用いて、UML“クラス”とJava “クラス”を完全に相互変換するようなことを最終目標と考えると、いろいろな要因が絡み合っていて、なかなか完全なマッピングができません。
この問題を解決するためには、開発の目的に応じて、プロファイルという形でまとめておく必要が出てきます。プロファイルを決めることによって、UML“クラス”とJava“クラス”のマッピングにおけるあいまいな点をなくすことができるため、完全なマッピングを行うことが可能になるのです。
本節では、このような目的から、通常の開発に利用するためのサンプルプロファイルをまとめてみました。
前述したようにUMLの“クラス”は、以下の種類に分けることができます。
この中で、普通のクラス、ステレオタイプutilityのクラス、インターフェイス、例外については、Javaとのマッピングははっきりしています。
ステレオタイプmetaClassのクラスに対応するJavaのクラスはjava.lang.Classのみです。ただし、JavaではメタクラスをベースとしたいわゆるMOP(Meta Object Protocol)のテクニックを使うことはできないので、ステレオタイプmetaClassのクラスを主要なモデル要素として考える必要はありません。もちろん、ステレオタイプmetaClassのクラスを補助的に使うのは有力です。
ステレオタイプthreadのクラスが対応するJavaクラスとしては、java.lang.Threadが本命です。しかし、java.lang.Runnableというインターフェイスもあり、単純にステレオタイプthreadのクラスを使うだけでは表現しきれません。実際にJava向けの設計モデルを作成する場合には、ステレオタイプthreadのクラスを使うよりも、java.lang.Threadやjava.lang.Runnableを直接指定する方が明快です。このため、本プロファイルでは、特にステレオタイプthreadのクラスについては直接のマッピングの対象にはしません。もちろん、ステレオタイプthreadのクラスを補助的に使うのは有力です。
シグナルは、Java側で対応するクラスはjava.lang.Throwableとなりますが、Javaプログラムではサブクラスのjava.lang.Errorや java.lang.Exceptionを使うのが一般的なので、特に意識しておく必要はないでしょう。
以上の点から、本プロファイルで使用する“クラス”は以下の5つとなります。これらの“クラス”のUML上の表現は図11となります。
実際の開発においては、以上の5つのクラスを部品として用いていけばよいでしょう。
具象クラスは、インスタンスを生成することができるクラスです。具象クラスのUMLでの実現例は図11の左上となります。四角形のクラスアイコン内に、クラス名がボールド体で記述されます。
このUMLクラスに対するJavaの実装はリスト1のようになります。
public class ConcreteClass { }
抽象クラスは、インスタンスを生成することができないクラスです。汎化を用いて利用されることを想定しています。
抽象クラスのUMLでの実現例は図11の上中央となります。
四角形のクラスアイコン内に、クラス名がイタリック体で記述されます。なお、クラス名をイタリック体で表示する代わりにプロパティabstractを明記する方法も有効です。
このUMLクラスに対するJavaの実装はリスト2のようになります。修飾子abstractを用いることで、抽象クラスを実現しています。
public abstract class AbstractClass { }
インターフェイスは、オペレーションの集合を示すモデル要素です。インターフェイスのUMLでの実現例は図11の右上となります。ステレオタイプinterfaceを用いてインターフェイスであることを示します。
このUMLインターフェイスに対するJavaの実装はリスト3のようになります。Javaのインターフェイスをそのまま用いることができます。
public interface Interface { }
ユーティリティは、グローバルなスコープを持つ変数や手続きをクラスの形にまとめたモデル要素です。 ユーティリティのUMLでの実現例は図11の左下となります。ステレオタイプutilityのクラスを用いてユーティリティであることを示します。
このUMLユーティリティに対するJavaの実装はリスト4のようになります。Javaでは、クラスメソッドのみを定義したクラスとして実現します。
public class Utility { public static Object doSomething(Object lhs, Object rhs) { ... } }
例外は、例外事象を示すシグナルです。プログラムの正規の制御フローとは異なった形で送受信されます。例外のUMLでの実現例は図11の下中央となります。ステレオタイプexceptionを用いて例外であることを示します。
このUML例外に対するJavaの実装はリスト5のようになります。図11では直接示されていませんが、ConcreteExceptionは、java.lang.Exceptionの子孫クラスである必要があります。
public class ConcreteException extends java.lang.Exception { }
またjava.lang.RuntimeExceptionの子孫クラスである場合には、プロパティruntimeを使って表現することになります。
属性のマッピングについて検討します。
UML属性に対応するJavaの部品として以下のものが挙げられます。
インスタンススコープを持ったUML属性はインスタンス変数、クラススコープを持ったUML属性はクラス変数にマップできます。UML属性とJavaのインスタンス変数/クラス変数の関係は、一般的なオブジェクト指向の守備範囲中の話なので大きな問題はありません。
少し議論が必要になるのはJavaの定数の扱いです。UMLでは直接定数に対応する概念がないため、簡単にマッピングすることができないのです。Javaの定数をUMLで表現する方法として以下のものが考えられます。
「属性を拡張して表現」は、専用のプロパティconstなどを導入し、定数であることを表現する方法です。この方法は、一番自然な解決策に見えますが、JavaのインターフェイスをUMLのインターフェイスにマッピングするときに問題となります。UMLのインターフェイスはオペレーションの集まりを表現するモデル要素であり、属性を持つことができないからです。
無難な方法として考えられるのは、オプションの並び区画を使う方法です。すなわち、定数宣言のための並び区画を新規に導入し、この中に定数の宣言を書いてしまいます。UMLの標準仕様外なのでJavaの文法のまま記述しても構いません。
本プロファイルでは、最後に挙げた「オプションの並び区画で定義」を採用することにします。
カテゴリ | UML | Java |
対応 | 属性名 | 属性名 |
型 | 型 | |
可視性 | 可視性 | |
初期値 | 初期値 | |
多重度 | 配列またはコレクションライブラリ | |
順序性 | 配列またはコレクションライブラリ | |
UML拡張プロパティ | プロパティfinal | final |
プロパティtransient | transient | |
プロパティvolatile | volatile | |
補助情報 | changeability | - |
persistence | - |
上の表は属性のプロファイルです。共通の部品である属性名、型、可視性、初期値はUMLの部品とJavaの部品がそのまま対応します。
UMLのスコープはJavaのstaticの有無に対応します。Javaではstaticで修飾された属性のスコープはクラススコープ、staticで修飾されていない属性のスコープはインスタンススコープになりますが、これがそのままUMLのスコープの定義に対応します。
多重度と順序性については、今回の検討範囲外とします。次回以降「アソシエーション」の回に検討する予定です。
UML側に拡張するプロパティは以下のものです。
Javaのtransientとvolatileに相当するUMLの機能はないので、この2つのプロパティを拡張します。
finalについては、UMLの標準プロパティであるchangeabilityと一部機能が重なっていますが、前述したようにまったく同じ機能を持つわけではありません。意味的な混乱を避けるのと、Javaプログラマにはfinalというプロパティがあれば意味が一目瞭然なので、本プロパティでは拡張プロパティfinalを導入することにしました。
UMLが標準で定義している以下のプロパティは補助情報とします。
changeablityは属性の変更可能性に対するプロパティ、persistenceはオブジェクトの永続性に対するプロパティです。いずれもJavaには完全に対応する機能がないため、補助情報という扱いにします。
一般的なプリミティブ型の属性のマッピングは以下のようになります。
Java | private int balance; |
---|---|
UML | balance:int |
Javaの可視性は、privateやpublicといった修飾子で記述されますが、UMLでは「-」や「+」といった記号が使用されます(UMLでもpublic
などの文字列を用いることはできますが、あまり一般的ではないので本連載では省略します)。この例では、JavaのprivateをUMLでは「-」で表現しています。
属性の型は、Javaでは変数名の左側に、UMLでは属性名の右側、「:」の後に記述します。この例ではJavaのプリミティブ型を指定していますが、これはUMLではデータ型にマップされます。
一般的なオブジェクトの属性のマッピングは以下のようになります。
Java | private String name; |
---|---|
UML | name:String |
基本的にはプリミティブ型と同じ構成になります。型としてクラス名が記述されます。
Java | private int balance = 0; |
---|---|
UML | balance:int = 0 |
Java、UMLいずれの場合も、「=」の右側に初期値を記述します。UMLでは初期値の文法は特に定めておらず、ターゲットとなるプログラミング言語の文法をそのまま利用することになっており、この例でもそのようになっています。
クラス変数のマッピングは以下のようになります。
Java | private static int numberOfItems; |
---|---|
UML | numberOfItems:int |
Javaのクラス変数は修飾子staticを用いて表現します。
Javaのクラス変数に対応するUMLのモデル要素はクラススコープの属性となります。クラススコープであることを、属性に下線を引くことで表現します。
オペレーションのマッピングについて検討します。
UMLのオペレーションに対応するJavaの部品は以下の3つです。
インスタンススコープのオペレーションがインスタンスメソッドに、クラススコープのオペレーションがクラスメソッドに対応します。ステレオタイプcreateのオペレーションがコンストラクタに相当します。
オペレーションの部品のマッピングを以下の表に示します。
カテゴリ | UML | Java |
対応 | オペレーション名 | クラス名 |
返却値型 | 復帰型 | |
可視性 | 可視性 | |
パラメタ | パラメタ | |
例外 | 例外 | |
プロパティabstract | abstract | |
UML拡張プロパティ | プロパティfinal | final |
プロパティsynchronized | synchronized | |
プロパティnative | native | |
プロパティstrictfp | strictfp | |
補助情報 | プロパティquery | - |
プロパティroot | - | |
プロパティleaf | - | |
プロパティconcurrency | - |
UMLとJavaの共通の部品である、オペレーション名/メソッド名、返却値型/復帰型、可視性、パラメタ、例外はそのままマッピングされています。また、UMLのプロパティabstractとJavaの修飾子abstractも同じ意味です。いずれも抽象メソッドを表します。
UML側に拡張するプロパティは以下のものです。
Javaのnativeとstrictfpに相当するUMLの機能はないので、この2つのプロパティを拡張するのは自然です。
finalについては、UMLの標準プロパティとして、同じ意味であるleafが定義されています。しかし、プロパティfinalはクラスや属性のところですでに導入しています。用途の一貫性を保つためには、オペレーションにおいてもfinalを用いた方が混乱が少ないということで、本プロファイルではプロパティfinalを用いることにします。プロパティsynchronizedは、標準プロパティconcurrencyと一部意味が重なりますが、前述したように完全に対応するわけではありません。意味的な混乱を避けるのと、Javaプログラマにはsynchronizedというプロパティがあれば意味が一目瞭然なので、本プロパティでは拡張プロパティsynchronizedを導入することにしました。
UMLが標準で定義している以下のプロパティは補助情報とします。
プロパティqueryとプロパティrootは、対応する機能がJavaにありません。
プロパティleafは、Javaのfinal修飾子に対応しますが、拡張プロパティfinalを導入する以上、Javaのfinal修飾子にはプロパティfinalをマップするのが整合性の意味から望ましいと考えられます。
プロパティconcurrencyは、Javaのsynchronized修飾子と微妙に意味が重なっていますが、完全に同じではないため、実用上は新規にsynchronized修飾子を導入した方が分かりやすいと考えられます。
それでは、本プロファイルでのオペレーションのマッピング例について見ていきましょう。
インスタンスメソッドのマッピングは以下のようになります。
Java | public int getBalance() |
---|---|
UML | +getBalance():int |
属性の場合と同様に、可視性として、Javaではpublicなどの修飾子、UMLでは「+」のなどの記号が用いられます(UMLでもpublicなどの文字列を用いることはできますがあまり一般的ではないので本連載では省略します)。Javaのメソッド名はそのままUMLのオペレーション名にマップされます。Javaのメソッドの復帰値にはプリミティブ型かオブジェクト型名が、メソッド名の左側に指定されますが、UMLではオペレーションの右側に「:」に続いて指定されます。
引数のあるインスタンスメソッドのマッピングは以下のようになります。
Java | public int plus(int lhs, int rhs) |
---|---|
UML | +plus(lhs:int, rhs:int):int |
Javaではメソッドの引数を「型」「パラメタ名」の組みを「,」で区切って並べます。それに対してUMLではオペレーションの引数は、「パラメタ名」「:」「型」の組みを「,」で区切って並べます。文法が若干異なりますが、記述している内容はほとんど同じですね。
復帰値がvoid型のインスタンスメソッドのマッピングは以下のようになります。
Java | public void doSomething(int order) |
---|---|
UML | +doSomething(order:int) |
Javaでは復帰値を持たないメソッドは復帰値の型としてvoidを指定します。それに対してUMLでは復帰値の型としては何も指定しないという記述方法を取ります。
例外のあるインスタンスメソッドのマッピングは図12のようになります。
Javaではメソッドが送出する可能性のある例外はthrows句によって宣言されます。それに対してUMLではオペレーションからステレオタイプsendのディペンデンシィとして記述されます(ディペンデンシィは分類子間の関係を表現するリレーションシップの一種です。詳細な内容については次回以降「ディペンデンシィ」 の回に取り上げる予定です。
抽象メソッドのマッピングは以下のようになります。
Java | public abstract int getBalance() |
---|---|
UML | +getBalance():int |
Javaでは抽象メソッドは修飾子abstractによって宣言されます。それに対してUMLではオペレーション名をイタリック体で記述することで表現します。またUMLでは以下のようにプロパティabstractを指定することでも抽象メソッドを表現できます。この記法はプレーンテキストなどイタリック体が使えない場合に有用です。
クラスメソッドのマッピングは以下のようになります。
Java | public static int getNumberOfItems() |
---|---|
UML | +getNumberOfItems():int |
Javaではクラスメソッドは修飾子staticによって宣言されます。それに対してUMLではオペレーション名に下線を引くことで表現します。
修飾子finalを持つメソッドのマッピングは以下のようになります。
Java | public final int getBalance() |
---|---|
UML | +getBalance():int {final} |
この例ではJavaにおける修飾子finalを、UMLの拡張プロパティfinalとして表現しています。なおオペレーションに対する標準プロパティleafは、Javaのfinalと同じ機能を持っているので、UMLの標準文法だけで表現するのであれば、以下のようになります。
修飾子nativeを持つメソッドのマッピングは以下のようになります。
Java | public native int getBalance() |
---|---|
UML | +getBalance():int {native} |
Javaの修飾子nativeを、UMLの拡張プロパティnativeで表現しています。
Copyright © ITmedia, Inc. All Rights Reserved.