システムモデリングには正確さが求められます。アーキテクトや開発者が複雑なソフトウェア構造を設計する際、コンポーネント間の関係がシステムの振る舞い、スケーラビリティ、変更への耐性を決定します。複合構造図内では、しばしば混乱を招く2つの関係タイプがあります:集約と構成。これらはどちらも部分-全体関係を表しますが、その違いは所有権、ライフサイクル管理、依存関係の強さを規定します。
これらのニュアンスを理解することは、単なる学術的なことではありません。メモリ管理の仕方、データの永続化方法、異なるサブシステム間の結合度に大きな影響を与えます。このガイドでは、基本的な定義を超えて、システム設計におけるこれらの構造的概念の実践的意味を深く探求します。

🏗️ 基盤:複合構造図
複合構造図は、分類子の内部構造を示します。分類子がネストされたコンポーネントにどのように分割されているか、そしてそれらのコンポーネントがポートや接続子を通じてどのように相互に作用するかを描きます。この内部構造において、部分が全体にどのように接続されているかが非常に重要です。
複雑なアセンブリを想像してください。中心となるユニットがあり、それに小さなユニットを接続します。場合によっては、中心ユニットが破壊されても、小さなユニットは残ります。他方で、中心ユニットが破壊されると、小さなユニットも存在できなくなります。この違いこそが、集約と構成の本質的な違いです。
- 複合構造図内部アーキテクチャに注目する。
- 部分-全体関係これら内部部品の接続方法を定義する。
- 所有権部分のライフサイクル管理を誰が担うかを決定する。
🤝 集約:弱い部分-全体関係
集約は、1つのオブジェクト(全体)が別のオブジェクト(部分)を含むか参照する関係を表しますが、その部分は独立して存在可能です。これはしばしば「共有」または「弱い」関係と表現されます。この状況下では、部分のライフサイクルが全体のライフサイクルに厳密に束縛されるわけではありません。
🔍 集約の主な特徴
- 独立性:部分は全体がなくても存在可能である。
- 共有所有:部分は複数の全体に同時に属している可能性がある。
- 弱い結合:全体の変更が部分の存在に必ずしも影響を与えるわけではない。
- 方向性:通常、全体側に開いたダイヤモンドを描いた線で表現される。
大学とその学部を想定してみましょう。学部は大学構造内に存在します。しかし、大学が特定の建物を閉鎖した場合、学部オブジェクト自体はアーカイブ目的でデータベースやメモリに残る可能性があります。あるいは、別の管理単位に再割り当てされるかもしれません。より正確には、チームとその選手を考えてみましょう。チームが解散しても、選手は個人として存在し続けます。別のチームに加入できます。選手は厳密なライフサイクルの観点から、チームに独占的に所有されているわけではありません。
🧩 実装上の影響
集約をモデル化する際、依存関係は認めますが、生成依存関係は認めません。全体を管理するコードやロジックが、部分をインスタンス化する必要はありません。部分はインジェクションされ、引数として渡される、または共有プールから取得される可能性があります。これにより、初期化ロジックの複雑さが軽減されます。
実装に関する重要なポイント:
- コンストラクタ依存なし:全体のコンストラクタ内で部分を作成する必要はない。
- 参照の渡し方 全体は部分への参照(ポインタまたはID)を保持している。
- ガベージコレクション: 全体を破棄しても、部分の破棄は自動的に発生しない。
💥 コンポジション:強い部分-全体関係
コンポジションは、集約のより強い形を表す。排他的な所有を意味する。部分は全体の不可欠な構成要素であり、そのライフサイクルは全体のライフサイクルと厳密に結びついている。全体が破棄されれば、部分もそれに伴って破棄される。
🔍 コンポジションの主な特徴
- 依存性: 部分は全体が存在しなければ成立しない。
- 排他的所有: 部分は一度に一つの全体にのみ所属する。
- 強い結合: 全体の作成と破棄が、部分の作成と破棄を決定する。
- 方向性: 全体側に黒塗りのダイアモンドがある線で表される。
家とその部屋を考えてみよう。部屋は家の存在によって定義される。家が取り壊されれば、その文脈内では部屋は機能的な存在として存在できなくなる。部屋を一つの家から別の家に移すことは、本質的にその存在の意味を変えることになる。同様に、車とそのエンジンを考えてみよう。エンジンは修理のために取り外すことはできるが、車の存在という文脈において、特定のエンジンインスタンスは不可欠な存在である。車が廃棄されれば、その特定のエンジン構成は実質的に失われる。
🧩 実装上の影響
コンポジションをモデル化する際、全体は部分の存在に対して責任を持つ。これは通常、全体内部でのインスタンス化に相当する。
- コンストラクタ依存: 全体は通常、初期化の際に部分を作成する。
- リソース管理: 全体は、全体が破棄された際に部分に割り当てられたリソースが解放されることを保証しなければならない。
- ライフサイクルの同期: 部分は複数の全体間で共有できない。
⚖️ 集約とコンポジション:詳細な比較
違いを明確にするために、これらの概念を並べて見比べることができる。以下の表は、システムアーキテクチャや図式化に関連する運用上の違いを分解したものである。
| 特徴 | 集約 | コンポジション |
|---|---|---|
| 所有権 | 共有または弱い | 排他的 |
| ライフサイクル | 独立した | 依存する |
| 作成 | 全体の外部 | 全体の内部 |
| 破壊 | 全体が死ぬ → 部分は生きる | 全体が死ぬ → 部分も死ぬ |
| 関連 | 多方向関連が可能 | 厳格な一方通行の所有権 |
| 記号 | 空心のダイヤモンド (◇) | 実心のダイヤモンド (◆) |
| 類似 | チームと選手 | 家と部屋 |
🛠️ コンポジット構造図における視覚的表記
コンポジット構造図では、これらの関係は分類子の内部部品間の特定の接続子を使って可視化される。この表記により、開発者やアーキテクトはコードを読まずに構造的制約をすばやく理解できる。
- 接続子: コンテナ部品と含まれる部品を結ぶ直線。
- ダイヤモンド(集約): コンテナの側に空心のダイヤモンドがあると、集約を示す。これは、厳格な所有権を持たない「所有する」関係であることを示す。
- ダイヤモンド(合成): コンテナの側に実心のダイヤモンドがあると、合成を示す。これは、厳格な所有権を持つ「部分である」関係を示す。
視覚的記号は標準的であるが、その解釈は設計段階で割り当てられた意味に依存する。実心のダイヤモンドは契約を意味する:「私はこの部分の生命を責任を持って管理する。」
🔄 ライフサイクル管理と所有権ルール
これらの関係の最も重要な側面の一つは、オブジェクトのライフサイクルにどのように影響するかである。これは、メモリ管理、データベーストランザクション、リソースの破棄において特に重要である。
🗑️ 破壊シナリオ
コンテナオブジェクトがメモリまたはシステムから削除されたとき:
- コンポジションシナリオ: システムは、すべての構成された部分を再帰的に破壊する。ドキュメントにページがある場合、ドキュメントを削除するとすべてのページも削除される。システムはページを他の場所に保存しようとはしない。
- アグリゲーションシナリオ: システムは部分への参照を削除する。部分はシステム状態のまま残る。システムはデータ整合性を損なうような部分の孤立を防ぐ必要があるが、部分自体は破壊されない。
🔁 再割り当ての可能性
コンポジションは再割り当てを禁止する。部分は再作成または再構成せずに、一つの全体から別の全体に移動することはできない。アグリゲーションは再割り当てを許可する。リソース(プリンタなど)は複数のコンピュータによってアグリゲートされることがある。コンピュータAが電源を切られても、プリンタはコンピュータBで利用可能のままになる。
🌍 構造モデリングのための現実世界のシナリオ
これらの概念を実際の状況に根ざさせるために、企業システムでよく見られる抽象的なシナリオを検討しよう。
シナリオA:注文処理システム
注文管理システムにおいて、注文は注文項目.
- 関係:コンポジション。
- 理由: 注文項目は通常、注文がなければ意味を持たない。この特定のモデルでは、注文の文脈なしに単一のアイテムを独立して販売することは通常行われない。注文がキャンセル(破壊)された場合、それに関連する注文項目はアクティブな文脈から削除される。
シナリオB:従業員ディレクトリ
ある部門は従業員.
- 関係:アグリゲーション。
- 理由: 従業員は部門とは独立して存在する。彼らは休暇中、異動中、または解雇されている可能性がある。部門が再編された場合、従業員オブジェクトはそのまま残る。関係は所有関係ではなく、コレクションである。
シナリオC:ファイナンシャル・ポートフォリオ
A ポートフォリオは…を保持する株式.
- 関係: 集約。
- 理由: 株式は、どのポートフォリオがそれを保持しているかに関わらず、市場に存在する。単一の株式インスタンスは、複数のポートフォリオオブジェクトから参照される可能性がある。ポートフォリオを破棄しても、株式データは破棄されない。
🚧 共通の誤りと誤解
デザイナーはしばしばこれら2つの概念を混同し、意図した緩い結合が緊密な結合になるか、逆にその逆が生じる。以下は避けたい共通の誤りである。
- 組成がデータの永続性を意味すると仮定する: 組成はモデル内のライフサイクル関係を定義する。下位の実装がそれを強制しない限り、データベースの連鎖削除を保証するものではない。しかし、モデルは意図を反映すべきである。
- 共有リソースに組成を使用する: 2つのコンポーネントが単一のリソースインスタンス(データベース接続プールなど)を共有する必要がある場合、組成は誤りである。代わりに集約を使用すべきである。組成は共有を妨げる。
- 「部品」の定義を無視する: 組成構造図における「部品」とは、特定のインスタンスを指す。クラス自体をモデル化している場合、それはクラス関連をモデル化していることになる。クラス定義とインスタンス関係の違いを明確にしているか確認するべきである。
- 組成の過剰使用: 組成は強い依存関係を生み出す。これによりリファクタリングが難しくなる。メインアプリケーションにモジュールを組み込む場合、そのモジュールを交換する必要があると、メインアプリケーションの構造を再構築しなければならない。集約はより柔軟性を提供する。
📈 システム設計および保守への影響
集約と組成の選択は、ソフトウェアの長期的な保守性に影響を与える。チームがコードベースとどのように関わるかにも影響する。
🔒 結合度と一貫性
組成はコンテナ内の一貫性を高める。コンテナは部品の内部ロジックの責任を負うようになる。これはカプセル化の観点から一般的に良い。しかし結合度も高まる。コンテナは部品が存在しないと正しく機能できない。
集約は一貫性を低下させる。コンテナは部品に依存するが、部品は自らの独立した存在を持つ。これにより結合度が緩くなり、コンポーネントを個別にテストしやすくなる。
🧪 テスト戦略
ユニットテストはこれらの選択に影響を受ける。
- 組成: 全体をテストする際、部品を暗黙的にテストすることが多い。部品をモック化すると、全体の状態を再作成する必要があるかもしれない。ライフサイクルロジック(作成/破棄)のテストが必要になる場合がある。
- 集約: モックやスタブを簡単に注入できます。部品は外部にあります。これにより、コンテナの論理とは別に、部品の論理を独立してテストしやすくなります。
📝 決定のためのガイドライン
設計中に部品と全体の関係に遭遇した場合、正しい関係タイプを判断するために、これらの具体的な質問をします。
- 部品は全体なしで意味を持ちますか?
もしはい、アグリゲーションに傾けるべきです。もしいいえ、コンポジションに傾けるべきです。 - 部品は複数の全体に属することができるか?
もしはい、アグリゲーションが必要です。コンポジションでは複数の所有者が禁止されています。 - 部品の作成は誰の責任ですか?
もし全体が作成するなら、コンポジションが likely です。外部のマネージャーが作成するなら、アグリゲーションが likely です。 - 全体が削除されたらどうなりますか?
もし部品も削除されなければならないなら、コンポジションを使用します。部品が生存しなければならないなら、アグリゲーションを使用します。
🔗 他の図タイプとの相互作用
複合構造図は孤立して存在しません。これらの関係はクラス図にもよく現れます。
- クラス図:アグリゲーションとコンポジションを使用して、クラスの属性と関連を定義します。表記は同一です。
- シーケンス図:ライフサイクル関係は作成メッセージとして現れます。コンポジションでは、シーケンス内でコンテナから部品への「作成」メッセージが表示されることがあります。
- 配置図:物理的なノードはソフトウェアアーティファクトをアグリゲートする可能性があります。サーバーがアプリケーションをホストしている場合、それはアグリゲーションかコンポジションか?通常はアグリゲーションです。なぜなら、サーバーは複数のアプリをホストする可能性があり、アプリは移動できるからです。
🧠 オブジェクト指向設計のニュアンス
現代のプログラミング言語では、これらの概念は特定のパターンに対応します。
依存性の注入
依存性の注入は、自然にアグリゲーションをサポートする技術です。依存関係をコンストラクタやセッターに注入します。コンテナは依存関係を所有しません。これにより、テスト可能性と柔軟性が向上します。
値オブジェクト vs. エンティティ
ドメイン駆動設計では、値オブジェクトはしばしばエンティティにコンポジションされます。それらは独自の識別子を持たず、エンティティの文脈内でのみ存在します。これは古典的なコンポジション関係です。他のエンティティを参照するエンティティは、しばしばアグリゲーションを介して行います(例:顧客は多数の注文をアグリゲートする)。
🛡️ セーフティとデータ整合性
コンポジションを選択することで、データ整合性のための安全網が得られます。ライフサイクルを束縛することで、孤立したデータが蓄積されないことを保証できます。たとえば、「セッション」が「ユーザー コンテキスト」をコンポジションする場合、セッションを閉じることでコンテキストがクリアされることが保証されます。ここにアグリゲーションを使用すると、メモリやデータベースに古くなったデータが残る可能性があります。
しかし、アグリゲーションは誤った破壊から安全を提供します。たとえば、「レポートジェネレータ」が「データソース」をアグリゲートしている場合、ジェネレータをシャットダウンしてもデータソースは消去してはいけません。データソースはジェネレータの一時的な障害を生き残らなければなりません。
🔍 既存モデルの分析
レガシーダイアグラムをレビューする際、曖昧さに直面することがあります。曖昧な関係をどう解釈しますか?
- ライフサイクルロジックを探る: コードやデータベースのトリガーを確認する。Aを削除するとBも削除されるか?それこそがコンポジションを示している。
- 共有を確認する: Bが複数のAに存在するか?それこそがアグリゲーションを示している。
- 命名規則を確認する: ときには「Manager」はアグリゲーション(既存のリソースを管理する)を示すが、一方で「Builder」はコンポジション(リソースを生成する)を示すことがある。
🎯 構造的整合性の要約
アグリゲーションとコンポジションの選択は、根本的なアーキテクチャ上の意思決定である。これは責任の境界とシステム内の存在の流れを定義する。アグリゲーションは柔軟性と共有を可能にし、部品を独立した実体として扱い、グループ化できる。一方、コンポジションは厳格な境界を強制し、部品が全体の不可欠な一部であり、全体の破壊後に存続できないことを保証する。
これらの概念を複合構造図内で厳密に適用することで、ソフトウェアの実行時動作を正確に反映するモデルを作成できる。この明確さにより技術的負債が削減され、新規開発者のオンボーディングが簡素化され、システムの進化に堅固な基盤が提供される。
常に設計選択をコンポーネントのライフサイクル要件に基づいて検証する。正しいダイヤモンド記号を用いた適切な図は、開発ライフサイクルの後半で何時間ものデバッグ作業やアーキテクチャ上の混乱を回避する。
