抽象ファクトリーパターン

UMLクラス図

ソフトウェア エンジニアリングにおける抽象ファクトリ パターンは、具体的なクラスを指定せずに共通のテーマを持つ個別のファクトリのグループをカプセル化することで、具体的なクラスを課すことなく関連オブジェクトのファミリを作成する方法を提供する設計パターンです。 [ 1 ]このパターンによれば、クライアント ソフトウェア コンポーネントは抽象ファクトリの具体的な実装を作成し、ファクトリの汎用インターフェイスを使用してファミリの一部である具体的なオブジェクトを作成します。クライアントは、製品の汎用インターフェイスのみを使用するため、これらの各内部ファクトリからどの具体的なオブジェクトを受信するかはわかりません。[ 1 ]このパターンは、一連のオブジェクトの実装の詳細を一般的な使用法から切り離し、オブジェクトの作成がファクトリ インターフェイスで公開されるメソッドで実装されるため、オブジェクトの構成に依存します。[ 2 ]

このパターンを使用すると、実行時であっても、コードを変更することなく、具体的な実装を相互に交換できます。ただし、同様のデザインパターンと同様に、このパターンを使用すると、初期のコード記述において不要な複雑さと余分な作業が生じる可能性があります。さらに、分離と抽象化のレベルが高いほど、システムのデバッグと保守が困難になる可能性があります。

概要

抽象ファクトリーデザインパターンは、1994年に出版された『デザインパターン』に記載されている23のパターンの1つです。このパターンは、以下のような問題を解決するために使用できます。[ 3 ]

  • アプリケーションをオブジェクトの作成方法から独立させるにはどうすればよいでしょうか?
  • クラスは、それが必要とするオブジェクトが作成される方法から独立するにはどうすればよいでしょうか?
  • 関連オブジェクトまたは依存オブジェクトのファミリはどのように作成できますか?

オブジェクトを必要とするクラス内に直接オブジェクトを作成するのは柔軟性に欠けます。そうすると、クラスが特定のオブジェクトに縛られてしまい、後からクラスを変更せずにインスタンス化を変更できなくなります。また、他のオブジェクトが必要になった場合にクラスを再利用できなくなり、実オブジェクトをモックオブジェクトに置き換えることができないため、クラスのテストも困難になります。

ファクトリーとは、コード内でオブジェクトが構築される具体的なクラスの場所です。このパターンの実装は、オブジェクトの生成とその使用を分離し、具体的なクラスに依存せずに関連オブジェクトのファミリーを作成することを目的としています。[ 2 ]これにより、基本クラスを使用するコードを変更することなく、新しい派生型を導入できます。

このパターンでは、このような問題を解決する方法を説明します。

  • オブジェクトを作成するためのインターフェースを定義および実装して、オブジェクトの作成を別の (ファクトリ) オブジェクトにカプセル化します。
  • オブジェクトを直接作成するのではなく、オブジェクトの作成をファクトリ オブジェクトに委任します。

これにより、クラスはオブジェクトの作成方法に依存しなくなります。クラスは、オブジェクトの作成に使用するファクトリーオブジェクトを設定することができ、ファクトリーオブジェクトは実行時に交換できます。

意味

デザインパターンでは、抽象ファクトリーパターンを「具体的なクラスを指定せずに、関連または依存するオブジェクトのファミリーを作成するためのインターフェース」と説明しています。[ 4 ]

使用法

ファクトリーは作成するオブジェクトの具体的な型を決定し、ここで実際にオブジェクトが作成されます。ただし、ファクトリーは作成された具体的なオブジェクトへの 参照(例えばJavaではnew演算子によって)または抽象型のポインターのみを返します。

これは、クライアントがファクトリオブジェクトに目的の抽象型のオブジェクトを作成し、そのオブジェクトへの抽象ポインタを返すように要求することで、クライアントコードをオブジェクト作成から分離します。 [ 5 ]

DocumentCreator一例として、多数の製品 (およびcreateLetter()など) を作成するためのインターフェイスを提供する抽象ファクトリ クラスがありますcreateResume()。システムには、またはなど、DocumentCreatorクラスの派生した具象バージョンがいくつでも存在します。各バージョンは、またはなどの対応するオブジェクトを作成する、異なる実装の、およびを持ちます。これらの各製品は、クライアントが認識している、またはなどの単純な抽象クラスから派生しています。クライアント コードは、の適切なインスタンスを取得し、そのファクトリ メソッドを呼び出します。結果として得られる各オブジェクトは同じ実装から作成され、共通のテーマを共有します。クライアントは、具象ファクトリによって作成された特定のバージョンではなく、 抽象クラスまたはクラスの処理方法を知るだけで済みます。FancyDocumentCreatorModernDocumentCreatorcreateLetter()createResume()FancyLetterModernResumeLetterResumeDocumentCreatorDocumentCreatorLetterResume

ファクトリーは抽象型への参照またはポインタのみを返すため、ファクトリーにオブジェクトを要求したクライアントコードは、作成されたオブジェクトの実際の具体的な型を意識せず、その負担も負いません。しかし、抽象ファクトリーは具体的なオブジェクト(つまり具体的なファクトリー)の型を認識しています。例えば、ファクトリーは設定ファイルからオブジェクトの型を読み取ることができます。型は設定ファイルで既に指定されているため、クライアントは型を指定する必要はありません。具体的には、これは次のことを意味します。

  • クライアントコードは具象型に関する情報を持たず、関連するヘッダーファイルクラス宣言をインクルードする必要もありません。クライアントコードは抽象型のみを扱います。具象型のオブジェクトはファクトリーによって生成されますが、クライアントコードはそれらのオブジェクトに抽象インターフェースを介してのみアクセスします。[ 6 ]
  • 新しい具象型の追加は、クライアントコードを別のファクトリを使用するように変更することで実行されます。この変更は通常、1 つのファイル内の 1 行で済みます。変更後のファクトリは、異なる具象型のオブジェクトを作成しますが、以前と同じ抽象型のポインタを返すため、クライアントコードが変更されることはありません。これは、クライアントコードを変更して新しい型をインスタンス化するよりもはるかに簡単です。これを行うには、新しいオブジェクトが作成されるコードのすべての場所を変更するだけでなく、そのようなすべてのコード場所で新しい具象型を認識していることを確認する必要があります (たとえば、具象クラス ヘッダー ファイルをインクルードするなど)。すべてのファクトリオブジェクトがシングルトンオブジェクトにグローバルに格納され、すべてのクライアントコードがシングルトンを経由して適切なファクトリにアクセスしてオブジェクトを作成する場合、ファクトリの変更はシングルトン オブジェクトを変更するのと同じくらい簡単です。[ 6 ]

構造

UML図

抽象ファクトリー設計パターンのUMLクラス図とシーケンス図のサンプル。[7]
抽象ファクトリー設計パターンのUMLクラス図とシーケンス図のサンプル。 [ 7 ]

上記のUMLクラス図では、およびオブジェクトをClient必要とするクラスは、およびクラスを直接インスタンス化しません。 はオブジェクト作成用のインターフェースを参照するため、 はオブジェクトの作成方法(どの具象クラスがインスタンス化されるか)に依存しません。 クラスは、およびクラスを インスタンス化することでインターフェースを実装します。ProductAProductBProductA1ProductB1ClientAbstractFactoryClientFactory1AbstractFactoryProductA1ProductB1

UMLシーケンス図は実行 時の相互作用を示しています。オブジェクトはオブジェクトを呼び出し、オブジェクトはオブジェクトを作成して返します。その後、オブジェクトはを呼び出し、オブジェクトはオブジェクトを作成して返します。 ClientcreateProductA()Factory1ProductA1ClientcreateProductB()Factory1ProductB1

変種

1994年に『デザインパターン』で定義された抽象ファクトリーパターンの元々の構造は、抽象ファクトリーとそこから生成される抽象製品のための抽象クラスに基づいています。具体的なファクトリーと製品は、継承を用いて抽象クラスを特殊化したクラスです。[ 4 ]

より最近のパターン構造は、抽象ファクトリーと生成される抽象プロダクトを定義するインターフェースに基づいています。この設計では、継承を回避するために、主流のプログラミング言語におけるインターフェースまたはプロトコルのネイティブサポートを利用しています。この場合、具体的なファクトリーとプロダクトは、インターフェースを実装することで実現するクラスです。[ 1 ]

このC++23実装は、この本にある C++98 以前の実装に基づいています。

stdをインポートしますstd :: arrayを使用します。std :: shared_ptr使用します。std :: unique_ptr使用します。std :: vector使用しますenumクラスDirection : char { NORTH SOUTH EAST WEST };クラスMapSite { public : virtual void enter () = 0 ; virtual ~ MapSite () = default ; };クラスRoom : public MapSite { private : int roomNumber ; shared_ptr < array < MapSite , 4 >> sides ; public : explicit Room ( int n = 0 ) : roomNumber { n } {}~ルーム() =デフォルト;Room & setSide ( Direction d , MapSite * ms ) { sides [ static_cast < size_t > ( d )] = std :: move ( ms ); std :: println ( "Room::setSide {} ms" , d ); return * this ; }仮想void enter () override = 0 ;Room ( const Room & ) = delete ; Room &演算子= ( const Room & ) = delete ; };クラスWall : public MapSite { public :明示的なWall ( int n = 0 ) : MapSite ( n ) {}~() =デフォルト;void enter ()オーバーライド{ // ... } };クラスDoor : public MapSite { private : shared_ptr < Room > room1 ; shared_ptr < Room > room2 ; public : explicit Door ( int n = 0 , shared_ptr < Room > r1 = nullptr , shared_ptr < Room > r2 = nullptr ) : MapSite ( n ), room1 { std :: move ( r1 )}, room2 { std :: move ( r2 )} {}~ドア() =デフォルト;void enter ()オーバーライド{ // ... }Door ( const Door & ) = delete ; Door &演算子= ( const Door & ) = delete ; };クラスMaze { private : vector < shared_ptr < Room >> rooms ; public : Maze () = default ; ~ Maze () = default ;Maze & addRoom ( shared_ptr < Room > r ) { std :: println ( "Maze::addRoom {}" , reinterpret_cast < void *> ( r . get ())); rooms . push_back ( std :: move ( r )); return * this ; } shared_ptr < Room > roomNo ( int n ) const { for ( const Room & r : rooms ) { // 実際の検索ロジックはここに... } return nullptr ; } };クラスMazeFactory { public : MazeFactory () = default ;仮想~ MazeFactory () =デフォルト;[[ nodiscard ]] unique_ptr < Maze > makeMaze () const { return std :: make_unique < Maze > (); }[[ nodiscard ]] shared_ptr < Wall > makeWall () const { return std :: make_shared < Wall > (); }[[ nodiscard ]] shared_ptr < Room > makeRoom ( int n ) const { return std :: make_shared < Room > ( new Room ( n )); } [[ nodiscard ]] shared_ptr < Door > makeDoor ( shared_ptr < Room > r1 , shared_ptr < Room > r2 ) const { return std :: make_shared < Door > ( std :: move ( r1 ), std :: move ( r2 )); } };// createMaze に部屋、壁、ドアを作成するためのオブジェクトをパラメータとして渡した場合、別のパラメータを渡すことで部屋、壁、ドアのクラスを変更できます。これは Abstract Factory (99) パターンの例です。クラスMazeGame { public : Maze () = default ; ~ Maze () = default ;[[ nodiscard ]] unique_ptr < Maze > createMaze ( MazeFactory & factory ) { unique_ptr < Maze > maze = factory . makeMaze (); shared_ptr < Room > r1 = factory . makeRoom ( 1 ); shared_ptr < Room > r2 = factory . makeRoom ( 2 ); shared_ptr < Door > door = factory . makeDoor ( r1 , r2 ); maze -> addRoom ( r1 ) . addRoom ( r2 ) . setSide ( Direction :: NORTH , factory . makeWall ()) . setSide ( Direction :: EAST , door ) . setSide ( Direction :: SOUTH , factory . makeWall ()) . setSide ( Direction :: WEST , factory . makeWall ()) . setSide (方向::ファクトリー.makeWall ( )) setSide (方向:: EAST ファクトリー.makeWall ( )) setSide (方向:: SOUTH ファクトリー.makeWall ( )) setSide (方向:: WEST ドア);戻る迷路} };int main ( int argc , char * argv []) { MazeGame game ; unique_ptr < Maze > maze = game . createMaze ( MazeFactory ()); }

プログラムの出力は次のとおりです。

Maze::addRoom 0x1317ed0 Maze::addRoom 0x1317ef0 Room::setSide 0 0x1318340 Room::setSide 2 0x1317f10 Room::setSide 1 0x1318360 Room::setSide 3 0x1318380 Room::setSide 0 0x13183a0ルーム::セットサイド 2 0x13183c0ルーム::セットサイド 1 0x13183e0ルーム::セットサイド 3 0x1317f10

参照

参考文献

  1. ^ a b cフリーマン, エリック; ロブソン, エリザベス; シエラ, キャシー; ベイツ, バート (2004). ヘンドリクソン, マイク; ルーキデス, マイク (編). Head First Design Patterns (ペーパーバック) . 第1巻. オライリー. p. 156. ISBN 978-0-596-00712-6. 2012年9月12日閲覧
  2. ^ a bフリーマン, エリック; ロブソン, エリザベス; シエラ, キャシー; ベイツ, バート (2004). ヘンドリクソン, マイク; ルーキデス, マイク (編). Head First Design Patterns (ペーパーバック) . 第1巻. オライリー. 162ページ. ISBN 978-0-596-00712-6. 2012年9月12日閲覧
  3. ^ 「Abstract Factoryデザインパターン - 問題、解決策、適用性」w3sDesign.com . 2017年8月11日閲覧
  4. ^ a b Gamma, Erich; Richard Helm; Ralph Johnson; John M. Vlissides (2009-10-23). 「デザインパターン:抽象ファクトリー」 . informIT. 2012-05-16にオリジナルからアーカイブ。2012-05-16に取得。オブジェクト作成:抽象ファクトリー:意図:具体的なクラスを指定せずに、関連または依存するオブジェクトのファミリーを作成するためのインターフェースを提供する。{{cite web}}: CS1 maint: bot: 元のURLステータス不明(リンク
  5. ^ Veeneman, David (2009-10-23). 「Object Design for the Perplexed」 . The Code Project. 2011-02-21にオリジナルからアーカイブ。2012-05-16に閲覧ファクトリーは、製品やその作成方法の変更によるクライアントへの影響を遮断し、非常に異なる抽象インターフェースから派生したオブジェクト間でもこの遮断を実現できます。{{cite web}}: CS1 maint: bot: 元のURLステータス不明(リンク
  6. ^ a b「Abstract Factory: 実装」 . OODesign.com . 2012年5月16日閲覧
  7. ^ 「Abstract Factoryデザインパターン - 構造とコラボレーション」w3sDesign.com . 2017年8月12日閲覧