本記事は、 Qrunch からの移転記事です。
UMLのクラス図には、ノードのようなもの(クラスやインタフェースなど)とエッジのようなもの(関連や依存など)を記述できます。
さて、クラスやインタフェースをプログラミング言語に落とし込むには、あまり苦も無く変換できると思います。
私もクラス図とPerlの変換について、記事を書いたりしていました。
クラス図とPerlの対応付け: https://qiita.com/Morichan/items/cfa83fee6d451847ef85
しかし、エッジのようなもの(以降、関係と呼びます)は、汎化関係以外を書くのは難しいのが現状です。
C++への変換において、「Recovering UML class models from C++: A detailed explanation」では、関係をC++に対応付けするためには意味解析 (semancit analysis) をする必要がある、と書かれています。
ここでいう意味解析という意味ですが、恐らくは構文解析だけでは瞬時に変換できず、構文解析結果を元にメモ化テーブルなどを作り、作成したメモ化テーブルから総合的に判断する必要がある、という意味だと私は捉えました。
例えば、1つのクラス内に同じ名前のメンバ変数(Javaであればフィールドと呼びます)が複数存在する場合、コンパイルエラーとなりますよね。
本来であれば避けるべきですが、構文解析だけでは判断できません。
判断するためには、構文解析によってフィールドのリストをメモ化テーブルに入れておき、同じメンバ変数をメモ化テーブルから確認しなければなりません。
他にも連想配列を使うとか、いろいろ方法はあると思いますが、どちらにせよ構文解析「だけ」では無理だということです。
これを「意味解析をする必要がある」という意味で使っていると考えます。
さて、上記の論文において「意味解析をする必要がある」という意味が、私が捉えた意味だとするならば、構文解析を元に関係を変換できるのではないか、と考えることができます。
以降は、私が考えたサイキョーの変換ですので、深く考えずに読んでいただければと思います。
関係には、以下の6つがあります。
上部の関係が最も強く、下がるにつれて関係が弱くなります。
余談ですが、「関係」と言えば上記6つの関係を総称したものとして取り、「関連」と言えば関係の中の1種類のものとして取ります。
よく勘違いしやすいので気を付けましょう。
以降、各関係をどう捉えるか、Javaを例にして考えてみます。
いわゆるインタフェースを実装しているクラスの関係です。
クラス図では、下図のように記述します。
Javaにおいてはinterfaceとimplementの関係を書けばよいです。
interface ふわふわしたもの {
// ...
}
class 実装したもの implements ふわふわしたもの {
// ...
}
簡単ですね。
いわゆる継承クラスの関係です。
クラス図では、下図のように記述します。
クラス図では、抽象クラスとクラスの汎化関係、およびクラスとクラスの汎化関係の間に差異はありません。
Javaにおいてはextendsの関係を書けばよいです。
abstract class 抽象的なもの {
// ...
}
class 具象的なもの extends 抽象的なもの {
// ...
}
class より具象的なもの extends 具象的なもの {
// ...
}
こちらも簡単ですね。
つまり、実現と汎化については、構文解析レベルで判断可能です。
ですから、これ以降について考えてみると面白いですよ。
以降は答えがありませんので、各自考えてみてください。
is-part-of関係と呼ばれる関係です。
クラス図では、下図のように記述します。
ここで、関連端名として「部位A」という文字と、その左端に可視性の「-」 (private) が登場しました。
Javaで記述する際に利用するので、併せて覚えてください。
よく勘違いされる方がいますが、黒塗り菱形は持っている側に付きます。
持たれる側に付くわけではありません、少しややこしいですよね。
また、持たれる側の矢印はオプションです、あってもなくてもあまり意味はありません。
さて、Javaでどう書くかというと、私は「そのクラスだけがインスタンスを持っている」と捉えます。
class 部分的なもの {
// ...
}
class 全体的なもの {
private 部分的なもの 部位A = new 部分的なもの();
}
new演算子以降については、あっても無くても問題ありません。
あるいは、コンストラクタ内に記述するというのも手でしょう。
class 部分的なもの {
// ...
}
class 全体的なもの {
private 部分的なもの 部位A;
全体的なもの() {
部位A = new 部分的なもの();
}
}
さて、これだけだと「ふーん、そんなものか」だと思われると思いますが、次の集約を見れば違いがはっきりすると思います。
own-a関係と呼ばれる関係です。
クラス図では、下図のように記述します。
さて、コンポジションとの違いは、黒塗り菱形を白抜き菱形に変更しただけです。
この違いですが、is-part-of関係(つまり「全体的なもの」は「部分的なもの」の一部である関係)とown-a関係(つまり「持っているもの」は「持たれているもの」を所有している)という言葉の違いである、と考えました。
すなわち、Javaでどう書くかというと、私は「そのクラス以外にもインスタンスを(参照として)保持している」と捉えます。
class 持たれているもの {
// ...
}
class 持っているもの {
private 持たれているもの 所持品X;
持っているもの() {
所持品X = new 持たれているもの();
}
}
違い無しじゃないか!
と思われたあなた、正解です。
上記のクラス図では違いはありません。
しかし、下図でははっきり違いが見えると思います。
class 持たれているもの {
// ...
}
class 持っているもの {
private 持たれているもの 所持品X;
持っているもの(持たれているもの 所持品) {
所持品X = 所持品;
}
}
class もう1つの持っているもの {
private 持たれているもの 所持品Y;
もう1つの持っているもの(持たれているもの 所持品) {
所持品X = 所持品;
}
}
class 管理しているもの {
private 持っているもの 管理対象X;
private もう1つの持っているもの 管理対象Y;
private 持たれているもの 所持品Z = new 持たれているもの();
管理しているもの() {
管理対象X = new 持っているもの(所持品Z);
管理対象Y = new もう1つの持っているもの(所持品Z);
}
}
「管理しているもの」クラスのコンストラクタによって、「持っているもの」クラスのコンストラクタと「もう1つの持っているもの」クラスのコンストラクタに、所持品Zインスタンスを代入しています。
Javaではインスタンスは基本的に参照渡しのため、「持っているもの」クラスの所持品Xと「もう1つの持っているもの」クラスの所持品Yと「管理しているもの」クラスの所持品Zは全て同じインスタンスを参照していることになります。
これが、私の考える集約です。
has-a関係と呼ばれる関係です。
クラス図では、下図のように記述します。
実は、Javaにおいて一番ややこしい関係は、この関連だと考えます。
すなわち、Javaでどう書くかというと、私は「実現、汎化、コンポジション、集約、依存のいずれにもあたらないが、依存より強い関係を持つ」と捉えます。
class 落ちているもの {
// ...
}
class 取るもの {
void hoge() {
落ちているもの 物体P = new 落ちているもの();
}
}
物体Pはhoge()メソッド内でのみ生存できます。
つまり、「取るもの」クラスの属性にはあたりません。
基本的に、関連端名を書ける関係は、インライン属性として同じように記述できます。
コンポジションのクラス図における「全体的なもの」クラスと同義のインライン属性を持つクラスの図を、下図に示します。
これは逆に言えば、「全体的なもの」クラスの内部において、部位A属性をどの操作からでも参照できることを意味します。
しかし、関連における「取るもの」クラスの持つ物体P属性は、特定の操作内(メソッド内)でのみ生存するため、「取るもの」クラス内の別の操作が利用することはできません。
そのため、関連における関連端名をインライン属性には変換できません。
UMLの仕様ではどうだったか定かではありませんが、確か全ての関係はインライン属性に変換可能だったような気がします [要検証] 。
そのため、もしかしたらUMLの仕様とバッティングする恐れがあるので、この辺りは詳しく調査する必要がありそうです。
とても範囲の広い関係です。
クラス図では、下図のように記述します。
依存と聞くと、上記までの5つの関係全ても依存に書換えられる気がします。
しかし、UML2.5.1の仕様では、依存 (Dependency) は直接関係 (DirectedRelationship) と汎化関係であり、関連 (Association) と汎化 (Extension) とは異なるものとして定義されています。
実現 (InterfaceRealization) は依存と汎化関係であるため、実現については依存に書換えようと思えばできる関係と言えるでしょう。
ちなみに、集約 (Aggregation) とコンポジション (Composition) は、属性 (Property) の種類を表す列挙子 (AggregationKind) として設定できるみたいで、こちらも全く異なります。
UML2.5.1の仕様では確かに書換えられないようですが、実際問題として「依存」という名前だけではよくわかりません。
ではUML2.5.1の仕様を読めば解決するのかというと、そうでもなさそうです。
A Dependency is a Relationship that signifies that a single model Element or a set of model Elements requires other model Elements for their specification or implementation.
意訳: 依存は、とあるクラス(ここでは要素ではなくクラスと読んで差し支えない)の仕様や実装のために、別のクラスを必要としていることを意味する。
つまり、実装に必要であればいいようです。
ということで、Javaでどう書くかというと、私は「依存先のクラスをどこかしらで利用しており、上記5つの関係以外の場合全てを表す」と捉えます。
class 依存されているもの {
// ...
}
class 依存しているもの {
// ...
void hoge(依存されているもの 引数S) {
// ...
}
}
例えば上記のように、メソッドの引数の型について、現状クラス図では定義することが難しいです。
ですが、依存を書くことで、どこか関係性があるのだろうことは理解できます。
他にも、キャストのためだけに呼ばれているとか、調べればいろいろ利用法が出てきそうです。
どちらにせよ、クラス図からJavaに変換する際には利用できそうにありません。
Javaからクラス図に変換する際に利用する関係だと思います。
煩雑になりすぎてしまう問題は、容易に想像できます。
しかし、UMLはいわゆるダイアグラムですから、読みづらければ非表示にすればよいだけです。
全てを表示する必要はなく、ダイアグラムの利用目的のために表示内容を変えるのは手だと思います。
私がUMLを研究していて、いつか挑戦してみたいことを書いてみました。
もう修士課程は終盤直前ですが、もしこの話に興味があれば、勝手に使ってかまいません。
もちろん、このサイトから引用したなんて野暮なことは書かず、自分で定義し、その理由付けも自分で行ってくださいね。
その論文ができたら、ご一報くださると嬉しいです。
UMLに未来があることを願います。
第1回 | 実践UML記述法:UMLのクラス図における関係の考察 |
第2回 | 実践UML記述法:スクリプト言語(Python, Perl, PHPなどなど)における可視性をUMLで記述する1考察 |
第3回 | 実践UML記述法:ユースケース図におけるユースケース間の関係の書き方を疑似コードから逆変換して考える |
第4回 | 実践UML記述法:シーケンス図で継承元(親クラス)のメッセージ(メソッド)を利用する際の書き方について |
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント