堅牢なソフトウェアシステムを設計するには、コードを書くこと以上に、設計図が必要である。統合モデル化言語(UML)がその設計図を提供し、その言語の中でクラス図は最も重要な構造的ツールである。クラス図は、クラス、その属性、操作、およびオブジェクト間の関係を定義することで、システムの静的構造を捉える。しかし、図を描くことはあくまで始まりにすぎない。真の価値は、既に確立されたUMLクラス図パターン。これらのパターンは、一般的なモデル化の問題に対する再利用可能な解決策を提供し、設計が保守可能でスケーラブルであり、すべてのステークホルダーにとって明確であることを保証する。
本書では、オブジェクト指向設計で用いられる基本的なパターンについて探求する。継承の構造、関係の管理、特定のツールに依存せずに構造的制約を実装する方法を検討する。これらのパターンを理解することで、複雑な論理を正確かつ説得力を持って伝える図を構築できる。

UMLクラス図の基礎を理解する 📐
特定のパターンに取り組む前に、基本的な構成要素を確実に理解しておく必要がある。クラス図は、システムの特定の時点におけるスナップショットを表す。各長方形はクラスを表し、3つのセクションに分けられる:
- 名前:クラスの識別子で、通常は大文字で表される。
- 属性:クラスインスタンス内に格納されるデータプロパティ。
- 操作:クラスが実行できるメソッドまたは関数。
可視性修飾子は、これらの要素がどのように相互作用するかを定義するために重要である:
- パブリック (+):他のすべてのクラスからアクセス可能。
- プライベート (-):クラス自身の中でのみアクセス可能。
- プロテクト (#):クラスおよびそのサブクラス内でアクセス可能。
- パッケージ (~):同じパッケージまたは名前空間内でアクセス可能。
さらに、属性や操作は静的またはインスタンスベースのものに分類できる。静的メンバーは特定のオブジェクトではなく、クラス自体に属する。図では、静的メンバーは通常下線が引かれる。この基礎的な知識は、複雑なパターンを効果的に適用するための前提条件である。
継承と一般化のパターン 🔗
継承により、新しいクラスが既存のクラスからプロパティや振る舞いを引き継ぐことができる。これによりコードの再利用が促進され、意味的な階層構造が確立される。UMLでは、この関係は、空心の三角形の矢印がスーパークラスを向く実線で表される。
一般化のパターン
一般化のパターンは、階層設計の基盤である。このパターンは、「このクラスは、そのクラスの特殊化されたバージョンであるか?」という問いに答える。
- 単一継承:クラスが一つの親クラスから継承する。これは多くのオブジェクト指向言語で最も一般的なパターンである。階層を平坦に保ち、ナビゲーションを容易にする。
- 多重継承: クラスは複数の親クラスから継承する。強力ではあるが、どの親クラスのメソッドを実行すべきかという曖昧さが生じる「ダイアモンド問題」を引き起こす可能性がある。UMLでは、この関係は子クラスに到達する空心の三角形を持つ複数の実線で示される。
- 抽象クラス: これらのクラスは直接インスタンス化できない。他のクラスのテンプレートとして機能する。図では、クラス名は斜体で示される。抽象メソッドもまた斜体で表示される。
継承を使うべきタイミング
明確な「〜は〜である」関係がある場合に継承を使用する。たとえば、正方形 は 長方形である。『持つ』関係には継承を使用しないようにする。これは、合成を継承より優先する原則に反するからである。
関係パターン:関連、集約、合成 🧩
関係はクラス同士がどのように相互作用するかを定義する。関連、集約、合成の違いを明確にすることは、正確なモデル化にとって不可欠である。これらのパターンは、関与するオブジェクトのライフサイクルと所有権を定義する。
関連
関連は、2つのクラス間の構造的関係を表す。これは、一方のクラスのオブジェクトが、他方のクラスのオブジェクトを認識していることを意味する。これは最も基本的な関係である。
- 表現方法: 2つのクラスを結ぶ実線。
- ロール名: 線上に付くラベルは、それぞれのクラスの視点から見た関係を説明する。
- 多重度: 数値または範囲(例:0..*、1..1)は、何個のインスタンスがリンク可能かを示す。
集約と合成の違い
集約と合成の両方とも、関連の特殊な形であり、全体と部分の関係を表す。主な違いは所有権とライフサイクルにある。
| 特徴 | 集約 | 合成 |
|---|---|---|
| 所有権 | 弱い所有権 | 強い所有権 |
| ライフサイクル | 部分は全体が存在しなくても存在可能 | 部分は全体が存在しなければ存在できない |
| UML記号 | 空洞のダイヤモンド | 塗りつぶされたダイヤモンド |
| 例 | 部署と教授 | 家と部屋 |
集約:大学とその学生を想像してみてください。大学が閉鎖されても、学生は依然として存在します。彼らは関連しているが、所有されているわけではないのです。空洞のダイヤモンドは、線の「全体」側に配置されます。
合成:車とそのエンジンを考えてみましょう。車が破壊されると、エンジンはその特定の車インスタンスの機能的な一部ではなくなってしまいます。ライフサイクルが結びついています。塗りつぶされたダイヤモンドは、「全体」側に配置されます。
静的コンテキストにおける生成パターン 🛠️
多くの生成パターンは行動的ですが、クラス図では構造的な表現を持ち、特に静的メソッドや属性を含む場合が多いです。これらのパターンは、オブジェクトの生成方法を管理します。
シングルトンパターン
シングルトンパターンは、クラスが唯一のインスタンスを持つことを保証し、グローバルにアクセスできるポイントを提供します。これは設定マネージャーやデータベース接続に一般的です。
- 構造: コンストラクタは外部からのインスタンス化を防ぐためにprivateです。
- アクセス: 静的メソッドで、通常は
getInstance()は単一のインスタンスを返します。 - 図示表現: クラス名は下線を引いて静的メンバーを示します。インスタンスを保持する属性は静的です。
この図を描く際は、属性が静的(下線付き)であることを確認し、メソッドも静的であるようにしてください。これにより、状態がオブジェクトではなくクラスに属していることが視覚的に伝わります。
ファクトリメソッドパターン
このパターンは、オブジェクトを作成するためのインターフェースを定義しますが、どのクラスをインスタンス化するかはサブクラスが決定できるようにします。これにより、クラスがインスタンス化のロジックをサブクラスに委譲できるようになります。
- 作成者: ファクトリメソッドを宣言する抽象クラスまたはインターフェース。
- 具体的な作成者: ファクトリメソッドを実装し、具体的な製品のインスタンスを返します。
- 製品: 作成されるインターフェースまたはクラス。
図では、Productインターフェースを返すメソッドを持つCreatorクラスが表示されます。これにより、クライアントコードが具体的なクラスから分離され、システムの柔軟性が向上します。
構造パターンとインターフェース 🛡️
構造パターンは、クラスがどのように組み合わされて大きな構造を形成するかに注目します。インターフェースはここでの大きな役割を果たし、実装の詳細を含まずに契約を定義します。
インターフェースの実装
インターフェースは、クラスが実装しなければならない操作の集合を定義します。契約を強制する手段です。UMLでは、破線と空洞の三角形を使って、インターフェースを指すように示されます。
- 関心の分離: インターフェースは、「何をすべきか」と「どのようにすべきか」を分離することを可能にします。
- ポリモーフィズム: 複数のクラスが同じインターフェースを実装でき、それらを相互に交換可能にします。
- 図示: インターフェースは、名前が <<Interface>> とスタereotypeされた別々のボックスとして示されることが多いです。実装線はクラスとこのボックスを結びます。
依存関係の注入
依存関係の注入は、オブジェクトが自身の依存関係を生成するのではなく、外部ソースから受け取る技術です。これにより結合度が低下します。
- コンストラクタ注入: 依存関係はクラスのコンストラクタを通じて渡されます。
- セッタ注入: 依存関係はパブリックなセッターメソッドを介して割り当てられます。
- 図示的な視点: クラスが依存関係の具体的なインスタンスを保持するのではなく、インターフェースへの参照を保持します。実際の実装は実行時において解決されます。
このパターンはテスト可能性とモジュール性を向上させます。図では、依存関係の矢印が具体的なクラスではなくインターフェースを指していることがわかります。
複数性と基数のルール 📊
クラス図で最もよく混乱する点の一つが複数性です。これは、あるクラスのインスタンスが、別のクラスのインスタンスに対していくつ関連するかを定義します。複数性の適切な使用により、ビジネスルールが明確になります。
- 1: 正確に1つのインスタンス。
- 0..1: 0個または1個のインスタンス(オプション)。
- 1..*: 1つ以上のインスタンス。
- 0..*: 0個以上のインスタンス(オプションのリスト)。
- 3..5: 3から5個のインスタンス(特定の制約)。
たとえば、顧客は複数の注文を発注できます。顧客から注文への関係は1..*です。逆に、注文はちょうど1つの顧客に属します。したがって、注文から顧客への関係は1です。これらの数値を関連線に記載することはオプションではなく、有効な設計の要件です。
保守性のためのベストプラクティス ✅
正確な図を描くことは一つのことですが、保守可能な図を描くことは別の問題です。これらの原則に従うことで、図が長期間にわたり有用であることが保証されます。
高い凝集性、低い結合度
これはソフトウェア設計の黄金法則です。
- 高い凝集性: クラスは1つの明確に定義された責任を持つべきです。クラスがデータベースのロジック、UIのレンダリング、ビジネスルールをすべて処理している場合、それは複雑すぎるのです。
- 低い結合度: クラスは具体的な実装よりも抽象化(インターフェース)に依存すべきです。これは、1つのクラスでの変更がシステム全体に波及しないことを意味します。
可視性のカプセル化
属性をプライベートに保ちましょう。必要なものだけをパブリックメソッドを通じて公開します。これにより、オブジェクトの内部状態が保護されます。図では、プライベートな属性(-)が多数あり、パブリックな操作(+)は少数であることがわかります。
一貫した命名規則
名前は意味のあるものにすべきです。業界標準でない限り、省略語を避けてください。クラス名にはPascalCase、メソッドや属性にはcamelCaseを使用してください。一貫性があることで、図を読む人の認知負荷が軽減されます。
避けたい一般的な落とし穴 ⚠️
経験豊富なデザイナーですらミスを犯します。これらの落とし穴に気づくことで、モデルをより良く仕上げることができます。
- 循環依存: クラスAがクラスBに依存し、クラスBがクラスAに依存しています。これによりループが発生し、初期化エラーを引き起こす可能性があります。インターフェースまたは中間クラスを使用して、この循環を解除してください。
- 過剰設計: 存在するすべての関係をモデル化するべきではありません。コアロジックに影響を与える関係に注目してください。あまりに複雑な図は読みにくくなります。
- 多重性を無視する:関与するオブジェクトの数を指定せずに線を引くと、設計が曖昧になります。常に基数を明示してください。
- 振る舞いと構造を混同する:クラス図は静的構造を示します。論理の流れや状態遷移をクラス図に示そうとしないでください。それらの目的には、シーケンス図または状態機械図を使用してください。
大規模システムにおける高度な考慮事項 🚀
システムが大きくなるにつれて、単一のクラス図は扱いにくくなります。複雑さを管理するための戦略を以下に示します。
パッケージ図
関連するクラスをパッケージにまとめます。これにより視覚的なごちゃごちゃを減らすことができます。パッケージ図は個々のクラスではなく、クラスのグループ間の依存関係を示します。
サブシステムとモジュール
サブシステムを、内部のクラス図を含む大きなボックスとして表現します。これにより内部の複雑さを隠しつつ、サブシステムがシステムの他の部分とどのように相互作用するかを示すことができます。サブシステムの境界を示すには破線を使用してください。
プロファイル拡張
ある分野では、標準のUMLでは不十分な場合があります。プロファイルを使用して言語を拡張できます。これにより、カスタムスタereotype、プロパティ、制約を追加できます。たとえば、データベースの文脈では、クラスに <<Table>> ステレオタイプを追加して、永続性マッピングを示すことができます。
主要な関係の要約
すばやく参照できるように、UMLクラス図で使用される主要な関係の要約を以下に示します。
- 依存関係(破線、開放矢印):1つのクラスが別のクラスを一時的に使用する(例:メソッドの引数)。
- 関連(実線):オブジェクト間の構造的リンク。
- 集約(空心のダイアモンド):部品が独立して存在できる「所有関係」。
- 合成(実心のダイアモンド):部品が全体に依存する強い「所有関係」。
- 一般化(実線、空心の三角形):「は〜である」継承関係。
- 実現(破線、空心の三角形):クラスがインターフェースを実装する実装関係。
これらのパターンを習得するには練習が必要です。小さなドメインからモデル化を始め、次に大きなシステムへと拡張してください。常に「この関係はビジネスルールを正確に反映していますか?」と問いましょう。答えが「いいえ」の場合、図を再描画してください。図は単なる技術的成果物ではなく、コミュニケーションツールです。開発者、アーキテクト、ステークホルダーすべてが理解できるようにしなければなりません。
これらの再利用可能な解決策を適用することで、オブジェクト指向設計が機能するだけでなく、洗練され、堅牢であることを保証できます。正確なクラス図を作成するための努力は、ソフトウェア開発ライフサイクルのコーディングおよび保守フェーズで大きな利益をもたらします。












