3.1.2. クラス図/class diagrams

クラス図では、検討対象のプログラム中に、どんなクラスが存在するのか、 また、クラスとクラスの間にどんな関係があるのかを図示します。 クラス図を作成することで、プログラム内の変数の過不足や、 変数が所属するクラスの妥当性を検討できます。

ここではクラス図の基本的な要素に絞って紹介します。

3.1.2.1. クラスの3つの区画/The 3 compartments of a class

クラス図では個々のクラスは長方形で表現します。

長方形は3つの区画(compartment)に分割され、上から名前区画、属性区画、 操作区画と呼びます。

名前区画にはクラス名、属性区画にはメンバ変数、操作区画にはメンバ関数を記述します。

../../../_images/class-3-compartments.png

Fig.1 クラスと3つの区画

変数やメソッドの名前の前の “+” や “-” はpublicやprivateといった可視性 (visibility)を表します。以下のような定義になっています。

記号

意味

+

public

-

private

#

protected

Fig.1 のクラス図に対応するC++のクラス定義の例を示します。 (C++での慣例に従って型の指定をクラス図よりも細かくしている箇所があります):

class VectorXYZ {
private:
  double x, y, z;
public:
  void setXYZ(double x, double y, double z);
  double getX() const;
  double getY() const;
  double getZ() const;
  double norm() const;
  double dot(const VectorXYZ &rhs) const; // dot product 内積。rhs = "right hand side"
  VectorXYZ cross_product(const VectorXYZ &rhs) const; // cross product 外積
};

個々の図面のスペースの都合で、属性区画や操作区画は省略されることがあります。

../../../_images/class-omit-sections.png

Fig.2 属性区画や操作区画、さらには双方を省略した例

3.1.2.2. メンバ変数/Member variables

クラス図ではメンバ変数の表現方法が2つ存在します。

1つは属性区画に記載する方法です。 メンバ変数の型がよく知られていて、図によって読者に説明する必要がない場合に使います。

もう1つの方法は、メンバ変数の型であるクラスを独立に図示して、クラスとクラスを 結ぶ線としてメンバ変数を表現する方法です。 メンバ変数の型となるクラスが、図解した方がよいと思われる場合に使います。 この方法では、Aクラスのメンバ変数としてBクラスの型の変数があったときに、 AクラスとBクラスそれぞれを図示して、間に実線を引きます。実線はクラスの間の対応関係 (クラス図の言葉では「関連:association」)があることを表現するためのものですが 殆どの場合、関連はメンバ変数に相当します。

それぞれについて説明していきます。

3.1.2.2.1. 属性区画に書くべきメンバ変数/Member variables that should appear in the attributes compartment

メンバ変数には型がありますが、その型の性質がよく知られていて、特に説明を 必要としないようなものである場合には、メンバ変数を属性区画に書きます。 変数の型が組み込みの数値型 int, double, char の場合や、標準ライブラリに属する stringやostreamのようなクラスである場合が該当します。さらにこれらの型の配列やポインタも 属性区画に書きます。

../../../_images/members-as-attributes.png

Fig.3 メンバ変数を属性区画に記載している例

これに対応するソースコードは、以下のようになります (このソースのままでは、どの変数もprivateなのにメソッド定義がないのでメンバ変数を どこからも参照できませんが):

class Molecule {
  double x, y, z;
  std::string label;
};

class Matrix4x4 {
  double data[4][4];
};

class MatrixMxN {
  double *data;
  int row_count;
  int column_count;
};

3.1.2.2.2. 関連として表現すべきメンバ変数/Member variables that should appear as associations

クラスに基づいて作成されるオブジェクトの間に1対1や1対nなどの対応関係がある場合に、 それらのクラスの間に関連があると言い、図の上ではクラスとクラスの間に 実線を引きます。さらに関連の種類に応じて線の端に飾りをつけることがあります。

関連は、若干抽象的な概念であり、関連をプログラム上でデータとして保持するための 変数のとり方にはいろいろなやり方がありますが、基本となるのは、関連を直接表した メンバ変数として記録する方法です。以下ではそのようなケースについて説明します。

関連している2つのクラスAとBが、「全体と部分」の関係にある場合には線に飾りを つけます。線分の端点のうち、全体の側のクラスの端にひし形の飾りをつけます。

ひし形には中抜きの白いひし形と、中塗りの黒いひし形があります。

「全体と部分」の結びつきが強く、全体の側のオブジェクトが(プログラム上の変数として) なくなると、部分の側のオブジェクトがもはや存在できないようなものの場合には、 黒いひし形を用います。 このような関連をコンポジションと呼びます。よい和訳はつけられていなようです。

../../../_images/composition.png

Fig.4(a) コンポジションの例

逆に全体と部分の間の結びつきが弱く、全体の側のオブジェクトが変数としてなくなった後でも 部分の側のオブジェクトが存続できるようなものの場合には白いひし型を用います。 このような関連を集約(aggregation)と呼びます。

../../../_images/aggregation.png

Fig.4(b) 集約の例

関連の線の終点側に、「始点から見たこの関連の名称」を書きます。 これは関連の始点側クラスのメンバ変数名になります。 アスタリスク「*」は、数量関係が、個数を明言しない「多」であることを 示しています。個数が決まっているなら具体的な数を書きます。個数を変数が 保持しているならば変数名を書きます。

強い結びつきのコンポジションと、弱い結びつきの集約では、コンポジションの方を よく使います。プログラムのデータ構造設計では、各オブジェクトのメモリ上の 生成・削除を司る持ち主として振る舞うクラスを一意に決めて設計することが多く、 それはコンポジションに該当します。

全体と部分の関係ではない関連を表すには、装飾のない実線を使います。 ただし、ポインタ変数の場合には、方向性があるのでポインタ変数が指している 向きに合わせて終点に矢印を書きます。Fig. 5 にそのような関連の例を示します。

../../../_images/plain-association.png

Fig.5 集約やコンポジションではない関連の例

クラス図での表記は2通りですが、C++の文法では、クラスAがクラスBを指す メンバ変数を持つ場合に、クラスBの値、ポインタ、参照とバリエーションがあります。 クラス図とは、次のように対応付けられます。

Aクラス内のメンバ変数宣言の形

クラス図で用いるべき表記

B var1; (B型の値)

B var2[100];

std::vector<B> var3;

コンポジション

B *var4; (B型のポインタ)

const B* var5;

B *var6[100]; B **var7

std::vector<B *> var8;

AクラスのデストラクタでBをdeleteして いればコンポジション。していなけ ればコンポジションでない。

B &var9; (B型の参照)

AクラスからこのBをdeleteすることは できないのでコンポジションでない。

クラス図を書いてから実装するのであれば、実装の段階でどれを使うか選ぶことに なります。既存のソースを元にクラス図を作図して整理するような場合には、 削除処理がどこで実装されているのかを確認して、コンポジションなのかどうか 判断しながら作図することになります。

../../../_images/members-as-relations.png

Fig. 6 メンバ変数をクラス間の関連で表現する例

Fig.6 に対応するソースコードの例を示します:

// 分子の物性データを保持するクラス
class MoleculeType {
  double mass;
  double A; // ポテンシャルモデル関数の係数
  double B; // 同上
};
// ひとつの分子のデータを保持するクラス
class Molecule {
  double x, y, z;
  std::string label;
  MoleculeType *moleculeType; // 分子の物性はmoleculeTypeが保持
};
// 分子の運動のシミュレーションをするクラス
class Simulator {
  Molecule molecules[10000];
  MoleculeType moleculeTypes[10]; // 10種類の分子の物性データがある
};

3.1.2.3. メソッド/methods

メソッドは、メソッド区画に書きます。 メンバ変数と同様に、型の指定の文法がUML独特のものになっています。

C++ではポインタ型、参照型、const型などの区別がありますが、 クラス図上ではそこを識別する必要性を鑑みて、省略してしまってもよいでしょう。 書きたい場合には、型名の後に *& などを書きます。constキーワード については作画ツールがうまく受け付けてくれないかも知れません。

../../../_images/methods.png

Fig.7 メソッド

3.1.2.4. クラススタティック変数・関数/class static variables and functions

メンバ変数やメンバ関数がクラススタティックであることを示すためには 名前に下線を引きます。

../../../_images/static-members.png

Fig.8 クラススタティック変数と関数

3.1.2.5. メンバ変数以外の手段による依存性

あるクラスBのメソッドを呼ぶ必要のあるようなクラスAでは、通常はBのポインタや参照 などをメンバ変数で持っておくので、クラス図上では関連の線が惹かれます。 それを見ると、Bのメソッドの利用者はAである、と判断できます。多くの場合、メンバ 変数を持っているクラスからはメソッドを呼び出します。

ところが、メンバ変数としては持っていないクラスに対して、メソッドを呼び出す ケースもあります。たとえば、Aクラスのメソッドの中でBクラスのオブジェクトを ローカル変数として作成し、そのメソッドを呼ぶような場合です。Aクラスのメソッド が終了する時点でBクラスのオブジェクトは消えてなくなります。

あるいは、Bがクラスがスタティックメソッドを提供する場合には、それを呼び出す Aの側に、B型の変数は必要ありません。

このままではクラス図を見ても、AクラスがBクラスを使っている事実が図に 表現されません。そのような場合、つまり、AからBに対して 関連はないのだけれども、Bの機能を利用している場合には、UMLではAからBに 依存性(dependency)がある、と言いクラス図上では破線の矢印で表現します。

../../../_images/dependency.png

Fig. 9 関連は無いが、依存性はある例

Fig.9 の例では、分子間力を計算するPotentialEnergyModelクラスが、クラススタティック 変数や関数を定義しています。分子間力のデータは物性データなので、このプログラムから 見れば定数定義を持っていて、それに基づく計算をする関数を提供する存在です。 どうせ1セットしかデータしかないので、シミュレーション対象の全Moleculeにそれぞれ PotentialEnergyModelへのポインタを持たせても、みんな同じ値を持つ事になります。 これでは変数を設ける意義がありません。そこで、PotentialEnergyModelの機能は スタティック関数として提供し、利用者側では特にPotentialEnergyModelに対して 関連を持たせてはいません。そこで、この関数の利用者を明示するために MoleculeからPotentialEnergyModelに対して依存性を図示しています。

あらゆる依存性を破線の矢印で表現する必要はありません。強調したい箇所や、 自明でないケースにおいて使います。

3.1.2.6. クラスの継承/Inheritance of classes

クラスとクラスの間に継承関係がある場合には、大きな三角形の矢じりのついた 矢印で、小クラスから親クラスを指します。

継承を用いる場合、仮想関数(プログラミング言語によっては「抽象メソッド」)も 合わせて使われることが典型的です。仮想関数はイタリック体で表します。

../../../_images/inheritance.png

Fig.10 クラス間の継承

3.1.2.7. クラス図作成のポイント

多数のクラスを一枚のクラス図に描くと、図が大きくなってしまいます。 見やすい大きさの図に留めるためには、一枚の図に登場させる情報量を加減する 必要があります。一枚の図に考えていることをなんでも書き込むのではなくて、 複数の図に分けて描くとよいでしょう。

一枚の図に登場させたいクラスの数が多い場合には、クラスごとの情報を落とすと よいでしょう。属性区画、操作区画を共に非表示にするとかなり小さくなります。

複数の図に分けて描くに当たっては、クラス図ごとにクローズアップするクラスの 集合や、クローズアップしたい処理を決めて、そこに関係の深い要素は細かく表現し、 関係の薄い要素は粗く表現することも、よく行われます。