ソフトウェアエンジニアリングにおいて、コンポジットパターンはパーティショニングデザインパターンです。コンポジットパターンは、同じタイプのオブジェクトの単一インスタンスと同じように扱われるオブジェクトのグループを記述します。コンポジットの目的は、オブジェクトをツリー構造に「構成」して、部分と全体の階層を表現することです。コンポジットパターンを実装することで、クライアントは個々のオブジェクトとコンポジットを統一的に扱うことができます。[ 1 ]
Composite [ 2 ] デザインパターンは、柔軟で再利用可能な オブジェクト 指向ソフトウェア、つまり実装、変更、テスト、再利用が容易なオブジェクトを設計するために、繰り返し発生する設計上の問題を解決する方法を記述した、23 のよく知られた GoF デザインパターンの 1 つです。
コンポジットパターンは、これらの問題を解決します。
(1)Partオブジェクトと (2)オブジェクトWholeのコンテナとして機能するオブジェクトを定義する場合Part、クライアントはそれらを別々に扱う必要があり、クライアントコードが複雑になります。[ 3 ]
Component部分Leafオブジェクトと全体Compositeオブジェクトのための統一インターフェースを定義します。LeafオブジェクトはComponentインターフェースを直接実装し、Compositeオブジェクトはリクエストを子コンポーネントに転送します。これにより、クライアントはComponentインターフェースを介してLeafオブジェクトCompositeを統一的に扱うことができます。 Leafオブジェクトはリクエストを直接実行し、Compositeオブジェクトはリクエストをツリー構造を下方向に再帰的に子コンポーネントに転送します。これにより、クライアントクラスの実装、変更、テスト、および再利用が容易になります。
下記のUMLクラス図およびオブジェクト図も参照してください。
ツリー構造のデータを扱う場合、プログラマはしばしばリーフノードとブランチを区別する必要があります。これによりコードが複雑になり、エラーが発生しやすくなります。解決策は、複雑なオブジェクトとプリミティブなオブジェクトを統一的に扱うことができるインターフェースです。オブジェクト指向プログラミングでは、コンポジットとは、類似の機能をすべて示す 1 つ以上の類似オブジェクトの組み合わせとして設計されたオブジェクトです。これは、オブジェクト間の「has-a」関係として知られています。[ 4 ]重要な概念は、オブジェクトの単一のインスタンスを操作するのと同様に、それらのグループを操作することができるということです。すべてのコンポジットオブジェクトに対して実行できる操作は、多くの場合、最小公倍数の関係を持っています。たとえば、グループ化された図形を画面に表示するシステムを定義する場合、図形のグループのサイズ変更が、単一の図形のサイズ変更と同じ効果(ある意味で)を持つように定義すると便利です。
複合オブジェクトは、クライアントがオブジェクトの合成と個々のオブジェクトの違いを無視する場合に使用する必要があります。[ 1 ] プログラマーが複数のオブジェクトを同じ方法で使用し、それぞれを処理するコードがほぼ同じである場合、複合オブジェクトは良い選択肢です。このような状況では、プリミティブと複合オブジェクトを同質のものとして扱う方が複雑ではありません。

上記のUMLクラス図では、クラスはとクラスを直接 (別々に)Client参照していません。代わりに、 は共通インターフェースを参照し、と を統一的に扱うことができます。 クラスには子がなく、インターフェースを直接実装しています。 クラスは子オブジェクト ( )のコンテナを保持し 、リクエストをこれら( ) に転送します。 LeafCompositeClientComponentLeafCompositeLeafComponentCompositeComponentchildrenchildrenfor each child in children: child.operation()
オブジェクト連携図は、実行時の相互作用を示しています。この例では、Clientオブジェクトがツリー構造内の最上位Compositeオブジェクト(型)にリクエストを送信します。リクエストは、ツリー構造を下方向にたどってすべての子オブジェクト(およびオブジェクト)Componentに転送され、実行されます。ComponentLeafComposite

子コンポーネントをコンテナに追加/削除する(add(child)/remove(child))や子コンポーネントにアクセスする()など、子コンポーネントに関連する操作を定義および実装するための設計バリアントは 2 つありますgetChild()。
Component扱うことができます。しかし、クライアントがオブジェクトに対して子オブジェクトに関連する操作を実行できるため、型安全性が失われます。LeafCompositeLeafComposite。クライアントはオブジェクトLeafと子オブジェクトを異なる方法で扱う必要があります。しかし、クライアントは子オブジェクトに対して子オブジェクトに関連する操作を実行できないCompositeため、型安全性が確保されます。LeafGoFの著者らは、型安全性よりも透明性を重視したコンポジット設計パターンの変種を提示し、2つのアプローチのトレードオフについて議論している。[ 1 ]
型安全なアプローチは、複合構造が構築後に固定される場合に特に有効です。構築コードは、複合構造を構築するために関連する型を知る必要があるため、透過性を必要としません。下流のコードが構造を変更する必要がない場合、子要素の操作操作をComponentインターフェース上に実装する必要はありません。


デザインパターンで説明されているように、このパターンでは、子操作メソッドをコンポジットサブクラスだけでなく、メインのコンポーネントインターフェースにも含める必要があります。最近の説明では、これらのメソッドが省略されている場合もあります。[ 7 ]
このC++23実装は、本書に掲載されているC++98以前の実装に基づいています。
import std ;using std :: runtime_error ; using std :: shared_ptr ; using std :: string ; using std :: unique_ptr ; using std :: vector ;// コンポーネント オブジェクト// 構成内のオブジェクトのインターフェースを宣言します。class Equipment { private : string name ; double netPrice ; protected : Equipment () = default ;explicit Equipment ( const string & name ) : name { name }, netPrice { 0 } {} public : // すべてのクラスに共通するインターフェースのデフォルトの動作を適切に実装します。[[ nodiscard ]] virtual const string & getName () const noexcept { return name ; }virtual void setName ( const string & name ) noexcept { this- > name = name ; }[[ nodiscard ]] virtual double getNetPrice () const noexcept { return netPrice ; }virtual void setNetPrice ( double netPrice ) noexcept { this- > netPrice = netPrice ; }// 子コンポーネントにアクセスして管理するためのインターフェースを宣言します。virtual void add ( shared_ptr < Equipment > ) = 0 ; virtual void remove ( shared_ptr < Equipment > ) = 0 ; virtual ~ Equipment () = default ; };// 複合オブジェクト// 子を持つコンポーネントの動作を定義します。class CompositeEquipment : public Equipment { private : // 子コンポーネントを格納します。using EquipmentList = vector < shared_ptr < Equipment >> ; EquipmentList equipments ; protected : CompositeEquipment () = default ;explicit CompositeEquipment ( const string & name ) : Equipment ( name ), equipments { EquipmentList ()} {} public : // コンポーネントインターフェースで子要素関連の操作を実装します。[[ nodiscard ]] virtual double getNetPrice () const noexcept override { double total = Equipment :: getNetPrice (); for ( const Equipment & i : equipments ) { total += i -> getNetPrice (); } return total ; }virtual void add ( shared_ptr < Equipment > equipment ) override { equipments . push_back ( equipment . get ()); }virtual void remove ( shared_ptr <Equipment> equipment ) override { equipments.remove ( equipment.get ( ) ) ; } } ;// リーフオブジェクト// コンポジション内のリーフオブジェクトを表します。class FloppyDisk : public Equipment { public : explicit FloppyDisk ( const String & name ) : Equipment ( name ) {}// リーフには子要素がありません。void add ( shared_ptr < Equipment > ) override { throw runtime_error ( "FloppyDisk::add() を呼び出すことはできません!" ); }void remove ( shared_ptr < Equipment > ) override { throw runtime_error ( "FloppyDisk::remove() を呼び出すことはできません!" ); } };class Chassis : public CompositeEquipment { public : explicit Chassis ( const string & name ) : CompositeEquipment ( name ) {} };int main () { shared_ptr < FloppyDisk > fd1 = std :: make_shared < FloppyDisk > ( "3.5インチフロッピー" ); fd1 -> setNetPrice ( 19.99 ); std :: println ( "{}: netPrice = {}" , fd1 -> getName (), fd1 -> getNetPrice );shared_ptr < FloppyDisk > fd2 = std :: make_shared < FloppyDisk > ( "5.25インチ フロッピー" ); fd2 -> setNetPrice ( 29.99 ); std :: println ( "{}: netPrice = {}" , fd2 -> getName (), fd2 -> getNetPrice );unique_ptr < Chassis > ch = std :: make_unique < Chassis > ( "PC Chassis" ); ch -> setNetPrice ( 39.99 ); ch -> add ( fd1 ); ch -> add ( fd2 ); std :: println ( "{}: netPrice = {}" , ch -> getName (), ch -> getNetPrice );fd2 -> add ( fd1 ); }プログラムの出力は
3.5インチフロッピーディスク:netPrice = 19.99 5.25インチフロッピーディスク:netPrice = 29.99 PCシャーシ:netPrice = 89.97 ' std :: runtime_error 'のインスタンスをスローした後にterminateが呼び出されました。内容:FloppyDisk :: add{{cite book}}: CS1 maint: 複数の名前: 著者リスト (リンク)