ガイドライン: 設計クラス
トピック
設計クラスは、システムの実装における 1 つまたは複数のクラスの抽象概念を表します。それが正確に何に対応するかは実装言語によって異なります。例えば、C++ のようなオブジェクト指向言語では、クラスは普通のクラスに対応付けられます。または、Ada では、クラスはパッケージの可視部分で定義されるタグ付きタイプに対応付けられます。
クラスはオブジェクトを定義し、オブジェクトはユース・ケースを実現 (実装) します。クラスは、システムに必要なオブジェクトに対するユース・ケースの実現による要求から発生するだけでなく、以前に開発したオブジェクト・モデルからも発生します。
よいクラスかどうかは、実装環境によって大きく異なります。クラスとそのオブジェクトの適切なサイズは、プログラミング言語などによって異なります。Ada を使う際に正しいと考えられることが、Smalltalk を使う場合には間違っている可能性があります。クラスは、実装言語の特定の現象に対応する必要があり、その対応からよいコードが生成されるように構造化されていなければなりません。
実装言語の特殊性が設計モデルに影響を与える場合でも、クラス構造は理解と修正が容易なように維持する必要があります。
実装言語がクラスとカプセル化をサポートしていなくても、クラスとカプセル化があるものとして設計する必要があります。
あるオブジェクトの属性や関係に、他のオブジェクトがアクセスしたり影響を与えたりできる唯一の方法は、そのオブジェクトの操作を通すことです。オブジェクトの操作は、そのクラスによって定義されます。特定の振る舞いは、操作を通して実行できます。この操作によって、オブジェクトが保持する属性や関係が影響を受け、他の操作が実行されることがあります。操作は、C++ のメンバー関数または Ada の関数やプロシージャーに対応します。
オブジェクトにどのような振る舞いを割り当てるかは、ユース・ケースの実現におけるそのオブジェクトのロールによって決まります。
操作の仕様では、パラメーターは形式パラメーターを構成します。各パラメーターには名前とタイプがあります。実装言語の構文とセマンティクスを使用して、操作とそのパラメーターを指定できます。その結果、コーディングの開始時には、これらが既に実装言語で指定されています。
例:
「リサイクル・マシン・システム」では、「受領の基本処理」クラスのオブジェクトは、あるタイプの顧客が投入したリサイクル品の数を把握しています。「受領の基本処理」オブジェクトの振る舞いには、返却されるオブジェクトの数を増加させることが含まれます。投入された品目への参照を受け取る操作 insertItem (品目の挿入) が、この目的を果たします。

操作を指定するときは、実装言語の構文とセマンティクスを使用します。
操作は、ほとんど常にオブジェクトの振る舞いを表します。クラスの振る舞いを操作で表すこともでき、その場合にはクラス操作となります。
UML では、操作のタイプの有効範囲を設定することで、これをモデル化できます。
操作には次の可視性を設定できます。
- public: 操作は、そのクラス以外のモデル要素からも見えます
- protected: 操作は、そのクラス、サブクラス、またはクラスのフレンドのみに見えます (言語依存)
- private: 操作は、そのクラスとクラスのフレンドのみに見えます
- implementation: 操作は、そのクラスの内部でのみ見えます
public な可視性は、操作が他のクラスから必要な場合にのみ、非常に制限して使用する必要があります。
protected な可視性を、デフォルトにする必要があります。この可視性は、外部のクラスによる使用から操作を保護し、振る舞いのゆるやかな結合とカプセル化を促進します。
private な可視性は、サブクラスによる操作の継承を防ぎたい場合に使用します。この可視性を使用すれば、スーパークラスからサブクラスを切り離し、未使用の継承操作を削除したり除外したりする必要性を減らすことができます。
implementation の可視性は最も制限的です。クラス自体だけがその操作を使用できるようにする場合に使用します。これは private な可視性の一種で、ほとんどのケースに適しています。
オブジェクトは、どのような状態にあるかにより、特定のメッセージに対する反応を変えることができます。オブジェクトの状態依存の振る舞いは、関連するステートチャート図で定義します。オブジェクトの各状態について、受け取ることができるメッセージ、実行できる操作、オブジェクトのその後の状態を、ステートチャート図に記述します。詳しくは、『ガイドライン: ステートチャート図』を参照してください。
コラボレーションは、オブジェクトの動的な相互作用のセットで、一群のオブジェクトが相互にメッセージを送りあって通信します。メッセージの送信は、Smalltalk では簡単に実現できます。Ada では、副プログラムの呼び出しとして実現できます。メッセージが受け取り側オブジェクトに送られると、オブジェクト内の操作が呼び出されます。メッセージでは、実行する操作の名前と必要なパラメーターが示されています。メッセージを送るときは、すべてのパラメーターについて、実際のパラメーター (形式パラメーターの値) を渡します。
ユース・ケースの実現におけるオブジェクト間のメッセージ送信と、操作が呼び出された時にオブジェクトが従う制御の焦点を、相互作用図に示します。詳しくは、「ガイドライン: シーケンス図」と「ガイドライン: コミュニケーション図」を参照してください。
属性は、名前が付いたオブジェクトのプロパティーです。属性名は、オブジェクトに関する属性のロールを説明する名詞です。オブジェクトの作成時に、属性に初期値を設定することができます。
属性のモデル化は、モデル化によってオブジェクトがより理解しやすくなる場合にだけ行います。属性としてのオブジェクトのプロパティーのモデル化は、プロパティーがそのオブジェクトのみのプロパティーである場合にだけ行う必要があります。そうでない場合は、そのプロパティーを表すオブジェクトのクラスに対する関連関係または集約関係と共に、プロパティーをモデル化する必要があります。
例:

属性をモデル化する方法の例です。家族の各構成員は、名前と住所を持っています。ここでは、「氏名」タイプの属性自分の名前と、「住所」タイプの属性自宅住所を識別しました。

この例では、属性の代わりに関連を使用します。自分の名前プロパティーは、おそらく家族の構成員ごとに固有のものです。したがって、属性タイプ「氏名」の属性としてモデル化できます。一方、住所は家族全員が共有するので、「家族」クラスと「住所」クラス間の関連としてモデル化するのが最もよい方法です。
ある概念を独立したオブジェクトとしてモデル化するのか、または他のオブジェクトの属性としてモデル化するのかということは、いつでも簡単にすぐ決定できるわけではありません。オブジェクト・モデル内に不必要なオブジェクトがあると、不必要な文書化が発生し、開発経費が増加します。したがって、ある概念がシステムに対してどの程度重要なのかを決定する基準を設定する必要があります。
- アクセス性: オブジェクトにするか属性にするかの選択を左右するものは、実生活における概念の重要性ではなく、ユース・ケース内でその概念にアクセスする必要性です。アクセス頻度が高い場合は、オブジェクトとしてモデル化します。
- 実行中の分離性: ユース・ケースの実行中に独立して扱う概念は、オブジェクトとしてモデル化します。
- 他の概念とのつながり: ある別の概念と密接な関係があり、独立して使用されることがなく、常にオブジェクト経由で使用される概念は、オブジェクトの属性としてモデル化します。
- 関係による需要: なんらかの理由で、あるユニットを 2 方向から関係付ける必要がある場合は、そのユニットを独立したオブジェクトにする必要があるかどうかを再検討します。2 つのオブジェクトが、ある属性タイプを持つ同一のインスタンスと関連することはできません。
- 発生頻度: あるユニットがユース・ケースの間だけ存在する場合は、オブジェクトとしてモデル化しません。代わりに、問題の振る舞いを実行するオブジェクトの属性としてモデル化するか、または影響を受けるオブジェクトの説明の中で記述するだけにします。
- 複雑さ: オブジェクトが属性のために複雑になりすぎる場合は、属性をいくつか抽出して、独立したオブジェクトにできます。ただし、オブジェクトが多くなりすぎないよう、適度な範囲に留める必要があります。一方、ユニットが非常にはっきりしている場合もあります。
例えば、(1) C++ の整数などのように、実装言語のプリミティブな型によって直接サポートできるくらい単純なユニットや、(2) C++ と Smalltalk-80 の文字列のように、実装環境のアプリケーションから独立したコンポーネントを使用して実装できるほど単純なユニットは、属性として分類されます。
システムが異なるとモデル化する方法も異なります。あるシステムでは、その概念が非常に重要でありオブジェクトとしてモデル化するかもしれません。別のシステムでは、その概念はそれほど重要ではなく、オブジェクトの属性としてモデル化するかもしれません。
例:
例えば、航空会社用に、出発をサポートするシステムを開発するものとします。

出発をサポートするシステム。空港にいる職員が出発をサポートするシステムを必要としているものと仮定します。各出発に対して、出発時刻、航空会社、目的地を定義する必要があります。これは、「出発」クラスのオブジェクトとしてモデル化でき、属性として出発時刻、航空会社、目的地を設定します。
システムを旅行代理店のために開発する場合、状況は多少異なります。

フライトの目的地は、独自のオブジェクト「目的地」としてモデル化されます。
出発時刻、航空会社、および目的地は、この場合もやはり必要です。ただし、旅行代理店は特定の目的地に向かう出発便を探すことに興味があるため、他の要求もあります。したがって、「目的地」用に独立したオブジェクトを作る必要があります。「出発」オブジェクトと「目的地」オブジェクトはお互いに認識している必要があり、この認識はこれらのオブジェクトのクラス間の関連で実現されます。
特定の概念の重要性についての議論は、クラス内で定義する必要のある属性を決定する際にも有効です。「車」というクラスでは、そのオブジェクトが乗用車登録システムで使われる場合と、自動車製造システムで使われる場合とでは、明らかに異なる属性が定義されます。
最後に、何をオブジェクトとして表し、何を属性として表すかという規則は、絶対的ではありません。理論的には、すべてをオブジェクトとしてモデル化できますが、これは実際的ではありません。簡単な経験則として、オブジェクトは、ある段階において、他のオブジェクトとは無関係に使用される何かと考えることができます。さらに、属性を使用してオブジェクトのすべてのプロパティーをモデル化する必要はありません。オブジェクトを理解するために必要なプロパティーだけをモデル化します。あまりにも実装固有のもので、実装担当者が処理する方が好ましいような詳細を、モデル化してはいけません。
クラス属性 
ほとんどの場合、属性はオブジェクトのプロパティーを表しています。クラスのプロパティーを属性で表すこともでき、その場合にはクラス属性となります。
UML では、属性のタイプの有効範囲を設定することで、これをモデル化できます。
オブジェクトは、オブジェクトが振る舞いを実行しなくても値が変化するものを、カプセル化できます。実際には外部ユニットであるけれども、アクターとしてモデル化されなかったものです。例えば、ある形態のセンサー機器がシステムの内側に来るようにシステムの境界が選択されたとします。このような場合は、センサーをオブジェクト内にカプセル化し、センサーが測定する値で属性を構成することができます。この値は、システム内の他のオブジェクトの影響を受けずに、連続的に、または一定の間隔で変化する場合があります。
例:
温度計をオブジェクトとしてモデル化できます。このオブジェクトには、温度を表す属性があり、周囲の温度変化に応じて値が変化します。他のオブジェクトは、この温度計オブジェクトにある操作を実行することによって、現在の温度を要求できます。

温度属性の値は、「温度計」オブジェクトの中で自発的に変化します。
このように変化するカプセル化された値を、通常の属性としてモデル化することもできますが、それが自発的に変化することをオブジェクトのクラスで記述する必要があります。
属性の可視性は次の値のいずれかです。
- public: 属性は、クラスを含むパッケージの内側からも外側からも見えます
- protected: 属性は、クラス自体、サブクラス、またはそのクラスのフレンドのみに見えます (言語依存)
- private: 属性は、クラス自体およびクラスのフレンドから見えます
- implementation: 属性は、そのクラスのみに見えます
public な可視性は、属性が他のクラスから直接アクセスできる場合にだけ、非常に制限して使用する必要があります。public な可視性を定義することは、実質的には、属性値を取得および設定する public な操作を関連付けて、属性の可視性を protected、private、または implementation として定義することの簡略記法です。属性の public な可視性は、これらの取得/設定操作を自動的に生成する必要があるということのコード生成プログラムに対する宣言として使用でき、クラス定義の時間の節約になります。
protected な可視性をデフォルトにする必要があります。この可視性は、属性を外部のクラスによる使用から保護し、振る舞いのゆるやかな結合とカプセル化を推進します。
private な可視性は、サブクラスによる属性の継承を防ぎたい場合に使用します。この可視性を使用すれば、スーパークラスからサブクラスを切り離し、未使用の継承属性を削除したり除外したりする必要性を減らすことができます。
implementation の可視性は最も制限的です。クラス自体だけがその属性を使用できるようにする場合に使用します。これは private な可視性の一種で、ほとんどのケースに適しています。
クラスの中には、複雑な抽象化/抽象概念を表し、複雑な構造を持つものもあります。
クラスをモデリングする際に、設計者は、内部に含まれる要素とそれらの関係を表現し、
それに従って実装担当者が該当クラス内で生じるコラボレーションを確実に実装できるようにしたい場合があります。
UML 2.0 では、クラスは内部構造や複数のポートを持つことができる構造化されたクラスとして定義されています。そして、
クラスは、接続されたパートの集合や、さらに細かい単位に分解できる場合もあります。クラスをカプセル化し、外部からのコミュニケーションが宣言済みのインターフェースに従うポートを必ず経由するようにすることもできます。
そのため、クラス図を使用してクラス関係 (関連、合成、集約など) および属性を表すのに加えて、設計者は複合構造図を使用することもできます。この図を利用して、設計者は、内部パートのインスタンスが所定のクラスのインスタンス内でどのようにしてその役割を果たすかを示すことができます。
このトピックの詳細と複合構造図の例については、『概念: 構造化されたクラス』を参照してください。
|