UMLにおける集約と構成:クラス図の関係を理解する

統一モデリング言語(UML)はソフトウェアアーキテクチャの設計図として機能する。利用可能な図のセットの中で、クラス図はシステムの静的構造を定義する基盤となる。クラス、属性、操作、そしてそれらを結びつける重要な関係を明示する。これらの関係の中でも、開発者やアーキテクトがしばしば混乱を招く2つの概念がある:集約および構成両者とも関連の形態を表すが、所有権およびライフサイクル管理に関する意味的重みが異なる。

正しい関係モデルを選択することは単なる文法的な好みではない。それはオブジェクトの相互作用の仕方、メモリ管理の方法、システムが障害や削除をどう扱うかを決定する。これらの関係を誤解すると、オブジェクトのライフサイクルが適切に管理されない脆弱なコードベースが生じ、不定参照やリソースリークを引き起こす。このガイドでは、集約と構成の微細な違いを解説し、設計に適用するための明確なフレームワークを提供する。

Chibi-style infographic comparing UML aggregation and composition relationships: hollow diamond symbol for aggregation (Department-Professor example, independent lifecycle, shared ownership) versus filled diamond for composition (House-Room example, dependent lifecycle, exclusive ownership), with visual comparison table, lifecycle management notes, and quick decision flowchart for software developers

🔗 基盤:関連の理解

集約と構成の違いを理解する前に、基本的な概念である関連を理解しなければならない。UMLでは、関連とは2つ以上のクラスの間の関係であり、それらがどのように相互作用するかを記述する。これは最も一般的な関係の形態である。

簡単なシナリオを考えてみよう:StudentクラスとCourseクラスがある。学生はコースに登録する。これは関連である。視覚的な表現は、2つのクラスを結ぶ実線である。多くの場合、関連には名前(例:「登録する」)や多重度(例:1対多)が付く。

関連はどのようにクラスが互いにどのようにやり取りするかを定義する。集約と構成はこれをさらに精緻化し、どのようにそれらが共に存在するかを定義する。これらは「部品-全体」関係を示す関連の特殊化である。しかし、その関係の強さは大きく異なる。

🔵 集約:弱い全体

集約は、あるクラスが別のクラスの一部である関係を表すが、その部分は全体から独立して存在できる。これはしばしば「持つ-ある」関係であり、弱い関係とされる。主な特徴は、子オブジェクトのライフサイクルが独立していることである。

視覚的表現

UMLのクラス図では、集約は「全体」クラスの終端に空洞のダイヤモンド形状を持つ実線でクラスを結ぶことで表現される。ダイヤモンドは包含クラス側を向く。

  • 記号:実線に空洞のダイヤモンド(◊)
  • 方向:ダイヤモンドは包含側に配置される。

ライフサイクルの独立性

集約の特徴は、ライフサイクルの独立性です。『全体』オブジェクトが破棄されても、『部分』オブジェクトは引き続き存在します。これらは共有リソースです。

以下を検討してください:部署教授.

  • 部署には多くの教授がいます。
  • しかし、部署が解体または解散しても、教授は存在を終えるわけではありません。
  • 教授は別の部署に移るか、大学全体から離れる可能性があります。

ここでは、部署が教授を集約しています。教授は部署に独占的に所有されているわけではありません。彼らは偶然その部署に関連している独立した実体です。

実装ロジック

オブジェクト指向プログラミングでは、これ often 依存性の注入や参照の渡しに翻訳され、コンテナのコンストラクタ内で新しいインスタンスを作成するのではなく、外部オブジェクトへの参照を保持します。

  • コンストラクタ: コンテナは部分を作成しません。
  • セッター: 部分は通常、セッター・メソッドを介して割り当てられます。
  • 破棄: コンテナが削除されると、参照は削除されますが、ガベージコレクタは部分を削除しません。

🔴 コンポジション:強い全体

コンポジションは、より強い関連の形です。部分が全体なしでは存在できない『部分-全体』関係を表します。排他的な所有モデルです。全体が破棄されると、部分も一緒に破棄されます。

視覚的表現

コンポジションは集約と視覚的に似ていますが、ダイヤモンドが塗りつぶされています。この塗りつぶされた形状は、結合の強さを示しています。

  • 記号: 塗りつぶされたダイヤモンド(◆)を伴う実線。
  • 方向: ダイヤモンドはコンテナ側に配置されます。

ライフサイクルの依存性

部分のライフサイクルは、全体のライフサイクルと厳密に結びついています。部分は全体と共に作成され、全体と共に破棄されます。

以下を検討してください: と a 部屋.

  • 部屋は家的一部分である。
  • 家が取り壊されれば、部屋は機能的な単位として存在しなくなる。
  • 部屋は、その境界を定義する構造体なしでは独立して存在できない。

もう一つの古典的な例は a と an エンジンエンジンは修理のために取り外すことができるが、車の論理構造の文脈では、エンジンは車の存在に不可欠な構成要素である。車が廃棄されれば、エンジンも廃棄される(またはそのプロセスの一環としてリサイクルされる)。厳密な構成において、エンジンは同じ論理スコープ内の他の車と共有されるリソースではない。

実装の論理

実装の観点から、構成はコンテナが部品の作成と破棄を担当することを意味する。

  • コンストラクタ: コンテナが部品のインスタンスを作成する。
  • スコープ: 部品はしばしばコンテナクラスのプライベートメンバである。
  • 破棄: コンテナが破棄されると、部品は明示的に破棄されたり、ガベージコレクションの対象となる。

📊 側面比較

違いを明確にするために、両関係の属性を構造化された形式で検討できる。

特徴 集約 構成
関係の種類 弱い「所有する」 強い「部分である」
視覚的記号 空洞のダイヤモンド (◊) 塗りつぶされたダイヤモンド (◆)
ライフサイクル 独立 依存
所有権 共有 排他的
作成 外部 内部
破壊 独立 全体と自動的に同期
部署 – 教授 家 – 部屋

🧠 ライフサイクル管理とメモリ

ライフサイクルの影響を理解することは、堅牢なソフトウェア設計にとって不可欠です。リソースが限られているシステムや手動メモリ管理を行うシステムでは、集約と構成の違いが、誰がクリーンアップを担当するかを決定します。

集約と共有参照

集約では、コンテナが参照を保持します。複数のコンテナが同じ子オブジェクトへの参照を保持する可能性があります。これは、共有サービスやグローバルレジストリを含むシナリオで一般的です。

  • シナリオ: A ユーザー オブジェクトとプロフィール オブジェクト。
  • 振る舞い: A ユーザープロフィール。別の SystemModuleもその同じものを参照している可能性がある Profile.
  • 意味するところ:もし Userが削除されると、ProfileSystemModule.

この関係が構成としてモデル化されていた場合、Userを削除すると、Profileが、SystemModuleの機能を損なう可能性がある。

構成と排他的な所有

構成はリソースのカプセル化を保証する。全体は部分の唯一の管理者である。これにより、システムの関係のない部分間の結合度が低下する。

  • シナリオ:ある Documentとその Pages.
  • 動作:ある ページは1つに属するドキュメント.
  • 意味するところ:もしドキュメントが閉じられると、ページデータは破棄される。他のオブジェクトがその特定のページインスタンスを保持してはならない。

このモデルは、親がもはや「所有」していない部分が変更されるといったデータ整合性の問題を防ぐ。責任の明確な境界を強制する。

🛠️ 実際の設計シナリオ

これらの概念を適用するには文脈が必要である。選択が重要になる具体的なシナリオを以下に示す。

1. ライブラリシステム

図書館を管理するシステムを想像してみよう。

  • 本と図書館(集約):本は図書館がなくても存在できる。売却されたり、紛失されたり、別の図書館に移動したりすることができる。図書館はその所蔵する本を集約する。
  • 本と会員(関連):会員が本を借りる。これは一時的な関連であり、構造的な関係ではない。

2. 金融口座

銀行アプリケーションを検討しよう。

  • 口座と取引(構成):取引記録は、それが所属する口座がなければ意味を持たない。口座が閉鎖されると、取引履歴は単位としてアーカイブされたり破棄されたりする。取引は口座の状態の一部である。
  • 口座と顧客(集約):顧客は複数の口座を持つことができる。1つの口座が閉鎖されても、顧客は依然として存在する。顧客は口座を集約する。

3. ユーザインターフェース

グラフィカルユーザインターフェースでは、ウィジェット構造がしばしば構成に依存する。

  • ウィンドウとボタン(構成): ウィンドウ内のボタンは、そのウィンドウのレイアウトの一部です。ウィンドウが閉じられれば、ボタンの状態は無関係になります。
  • ウィンドウとツールバー(集約): ツールバーは複数のウィンドウ間で共有されることがあります。1つのウィンドウが閉じられても、他のウィンドウでツールバーは利用可能のままです。

⚠️ 一般的な落とし穴と誤解

経験豊富なデザイナーですら、現実世界の概念をUMLの関係にマッピングする際に誤りを犯すことがあります。避けたい一般的な誤りを以下に示します。

1. 組成と継承の混同

組成(部分-全体関係)の方が適切な場合でも、継承(is-a関係)を使うのが魅力的です。継承は意味的な同一性を示します。組成は構造的依存関係を示します。

  • 誤り: を継承するエンジン.
  • 正しい: を含むエンジン(組成)。

継承はis-a関係を作ります。車はエンジンではありません。車はエンジンを持っています。これらを混同すると、保守が難しい深い継承階層が生じます。

2. 組成の過剰使用

厳格な組成は強力ですが、硬直性を生むことがあります。すべてを組成すると、柔軟性を失います。たとえば、すべてのクラスにLoggerを組み込むと、オブジェクトツリーを再構築せずにログ出力メカニズムを簡単に切り替えることができません。場合によっては、プラグ可能なコンポーネントには集約の方が適しています。

3. 多重性を無視する

ダイアモンド型の記号は、何個の部分があるかを教えてくれません。多重性(例:0..1、1..*、0..*)を明示する必要があります。組成は部分をゼロ個持つことも、複数個持つこともできます。関係の強さは同じですが、基数が構造を定義します。

4. 実装が図と一致すると仮定する

一般的な誤りは、UML図がコード実装と1行ずつ完全に一致しなければならないと仮定することです。UMLは仕様書ではなくモデルです。集約はC++ではポインタ、Javaでは参照を使って実装するかもしれません。図は意味的な意図を伝えるものであり、低レベルのメモリ管理とはわずかに異なる場合があります。

🔍 高度な考慮事項

基本的な定義を超えて、これらの関係がシステムの進化にどのように影響するかというアーキテクチャ上の意味があります。

依存関係の注入と集約

集約は依存関係の注入(DI)と自然に組み合わせられる。子オブジェクトは独立して存在するため、実行時にコンテナに注入できる。これによりテストとモジュール性がサポートされる。コンテナのライフサイクルに影響を与えずに、注入された依存関係を交換できる。

不変オブジェクトと合成

合成は不変データ構造でよく使われる。構造が部分から構成されており、全体が不変である場合、部分も通常不変となる。これにより、「全体」が作成された後は内部状態が変更できず、合成の契約を強化する。

再帰構造

集約と合成の両方とも再帰的になり得る。A フォルダファイルと他のフォルダを含む。これにより木構造が作られる。

  • 集約の再帰: フォルダは別の親に移動できる(共有存在)。
  • 合成の再帰: フォルダはディレクトリツリーの一部である。ルートが削除されると、すべてが削除される。

適切な再帰モデルを選択することは、削除操作の扱い方に影響する。合成は削除ロジックを単純化する(ルートを削除=すべて削除)。集約では、共有ノードを削除せずに参照をクリーンアップするため、走査が必要となる。

🎯 選択のためのガイドライン

クラス図を描いていて、この2つの選択肢のどちらを使うか迷っているときは、以下の具体的な質問を自分に投げかけよう。

  1. 部分は全体なしで存在するか?
    • はい ➔ 集約を使用する。
    • いいえ ➔ 合成を使用する。
  2. 部分は複数の全体に属することができるか?
    • はい ➔ 集約を使用する。
    • いいえ ➔ 合成を使用する。
  3. 部分の作成は誰の責任か?
    • 外部 ➔ 集約を使用する。
    • 内部(コンテナ) ➔ 合成を使用する。
  4. 全体が削除されたらどうなるか?
    • 部分が生存する ➔ 集約を使用する。
    • 部品が破損する ➔ コンポジションを使用する。

これらの質問は、抽象的なデザインパターンではなく、ビジネスロジックに基づいた具体的な意思決定を強いる。

📝 ベストプラクティスの要約

モデル化の明確さは、実装における曖昧さを防ぐ。高品質なクラス図を維持するための核心的なポイントを以下に示す。

  • 共有リソースにはアグリゲーションを使用する: オブジェクトが独立しており、再利用可能である場合。
  • 排他的な部品にはコンポジションを使用する: 部品の存在が全体なしでは意味を持たない場合。
  • 一貫性を保つ: パターンを決定したら、システム全体にわたって一貫して適用する。類似した関係に対して、明確な意味的根拠がない限り、アグリゲーションとコンポジションを混在させない。
  • 意図を文書化する: ライフサイクルが複雑な場合、図に注記を追加する。UMLはコミュニケーションツールである。
  • 定期的に見直す: 要件が変化すると、関係性も変化する可能性がある。ビジネスルールが共有部品の許可を変更した場合、コンポジションがアグリゲーションに変更される必要があるかもしれない。

これらの違いを習得することで、耐障害性があり、保守が容易で、論理的に整合性のあるシステムを構築できる。空洞のダイヤモンドと塗りつぶされたダイヤモンドの違いは視覚的に小さいが、ソフトウェアがデータと制御の流れをどのように管理するかという根本的な違いを表している。これらの細部に注意を払うことで、アーキテクチャが問題領域の真の性質を反映していることを確実にできる。