直接オブジェクト構築の代わりにファクトリクラスを使用する必要があるのはなぜですか?


162

GitHubとCodePlexでいくつかのС#とJavaクラスライブラリプロジェクトの歴史を見てきました。オブジェクトの直接インスタンス化とは対照的に、ファクトリクラスに切り替える傾向が見られます。

なぜファクトリクラスを広範囲に使用する必要があるのですか?クラスのパブリックコンストラクターを呼び出すことで、昔ながらの方法でオブジェクトが作成される、非常に優れたライブラリがあります。最後のコミットで、著者は数千のクラスのすべてのパブリックコンストラクターCreateXXXをすぐに内部に変更し、クラスの内部コンストラクターを呼び出して新しいオブジェクトを返すだけの数千の静的メソッドを持つ1つの巨大なファクトリークラスを作成しました。外部プロジェクトAPIは壊れており、よくできています。

なぜこのような変更が役立つのでしょうか?このようにリファクタリングのポイントは何ですか?パブリッククラスコンストラクターの呼び出しを静的ファクトリメソッド呼び出しに置き換えることの利点は何ですか?

いつパブリックコンストラクターを使用する必要があり、いつファクトリーを使用する必要がありますか?



1
ファブリッククラス/メソッドとは何ですか?
ドーバル

回答:


56

ファクトリクラスは、プロジェクトがSOLIDの原則により厳密に従うことができるため、しばしば実装されます。特に、インターフェイスの分離と依存関係の逆転の原則。

ファクトリーとインターフェースは、はるかに長期的な柔軟性を可能にします。これにより、より分離された、したがってよりテスト可能な設計が可能になります。このパスをたどる理由の完全なリストは次のとおりです。

  • Inversion of Control(IoC)コンテナを簡単に導入できます
  • インターフェイスをモックできるため、コードのテストが容易になります
  • アプリケーションを変更するときに柔軟性が高まります(つまり、依存コードを変更せずに新しい実装を作成できます)

この状況を考慮してください。

アセンブリA(->は依存することを意味します):

Class A -> Class B
Class A -> Class C
Class B -> Class D

クラスBをアセンブリAに依存するアセンブリBに移動します。これらの具体的な依存関係があるため、クラス階層全体のほとんどを移動する必要があります。インターフェイスを使用すると、痛みの多くを回避できます。

アセンブリA:

Class A -> Interface IB
Class A -> Interface IC
Class B -> Interface IB
Class C -> Interface IC
Class B -> Interface ID
Class D -> Interface ID

これで、クラスBをアセンブリBに移動しても、まったく苦労することはありません。アセンブリAのインターフェイスに依存します。

IoCコンテナーを使用して依存関係を解決すると、さらに柔軟性が高まります。クラスの依存関係を変更するたびに、コンストラクターの各呼び出しを更新する必要はありません。

インターフェイス分離の原則と依存関係の反転の原則に従うことにより、柔軟性の高い分離アプリケーションを構築できます。これらのタイプのアプリケーションのいずれかに取り組んだら、newキーワードの使用に戻ることは二度とありません。


40
IMO工場はSOLIDにとって最も重要ではありません。工場なしでSOLIDを問題なく実行できます。
陶酔

26
私にとって意味のないことの1つは、工場を使用して新しいオブジェクトを作成する場合、最初に工場を作成する必要があるということです。それで、それはあなたに何を正確にもたらしますか?自分でインスタンスを作成する代わりに、他の誰かがあなたに工場を提供すると仮定されていますか、それとも何か他のものですか?これは答えで言及する必要があります。そうでなければ、工場が実際にどのように問題を解決するかは不明です。
Mehrdad

8
@BЈовић-新しい実装を追加するたびに、ファクトリをクラックし、新しい実装を説明するためにそれを変更する-明示的にOCPに違反することを除いて。
テラスティン

6
@Telastynはい、ただし作成されたオブジェクトを使用するコードは変更されません。それは工場への変更よりも重要です。
BЈовић

8
工場は、答えが答えた特定の分野で役立ちます。それらはどこでも使用するのに適切ではありません。たとえば、文字列またはリストを作成するためにファクトリを使用することは、行き過ぎです。UDTの場合でも、必ずしも必要ではありません。重要なのは、インターフェイスの正確な実装を分離する必要がある場合にファクトリを使用することです。

126

同様whatsisname私はこれがの場合であると考えて、言ったカーゴ・カルトのソフトウェア設計。ファクトリー、特に抽象類は、モジュールがクラスの複数のインスタンスを作成し、このモジュールのユーザーに作成するタイプを指定できるようにする場合にのみ使用できます。この要件は実際には非常にまれです。ほとんどの場合、1つのインスタンスが必要なだけで、明示的なファクトリを作成する代わりに、そのインスタンスを直接渡すことができるからです。

実は、工場(およびシングルトン)は非常に簡単に実装できるため、必要のない場所でも多くの人が使用します。プログラマーが「このコードで使用する必要があるデザインパターンは何ですか?」彼の頭に浮かぶのはファクトリーです。

「たぶん、いつか、これらのクラスを異なる方法で作成する必要がある」ことを念頭に置いて、多くの工場が作成されます。これはYAGNIの明らかな違反です。

また、IoCは単なるファクトリーであるため、IoCフレームワークを導入すると工場は廃止されます。また、多くのIoCフレームワークは、特定のファクトリーの実装を作成できます。

また、CreateXXXコンストラクターのみを呼び出すメソッドを持つ巨大な静的クラスを作成するというデザインパターンはありません。そして、特にファクトリ(または抽象ファクトリ)と呼ばれていません。


6
「IoCは一種の工場」を除き、あなたの意見のほとんどに同意します。IoCコンテナーは工場ではありません。依存性注入を実現する便利な方法です。はい、一部は自動工場を建設することができますが、それらは工場自体ではなく、そのように扱われるべきではありません。また、YAGNIのポイントも主張します。ユニットテストでテストダブルを置き換えることができると便利です。事実の後にこれを提供するためにすべてをリファクタリングすることは、ロバの苦痛です。事前に計画を立て、「YAGNI」言い訳のために該当しない
AlexFoxGill

11
@AlexG-ええと...実際には、ほとんどすべてのIoCコンテナーがファクトリーとして機能します。
テラスティン

8
@AlexG IoCの主な焦点は、構成/慣習に基づいて他のオブジェクトに渡すための具体的なオブジェクトを構築することです。これはファクトリを使用するのと同じことです。また、テスト用のモックを作成できるようにするために工場は必要ありません。インスタンス化して、モックを直接渡すだけです。最初の段落で言ったように。ファクトリーは、インスタンスの作成をモジュールのユーザーに渡し、ファクトリーを呼び出す場合にのみ役立ちます。
陶酔

5
区別する必要があります。工場出荷時のパターンは、プログラムの実行時にエンティティを作成するために、消費者によって使用されます。IoCコンテナは、起動中のプログラムのオブジェクトグラフを作成するために使用されます。これは手動で行うことができますが、コンテナは便利です。工場の消費者は、IoCコンテナを認識しないでください。
AlexFoxGill

5
Re:「そして、テスト用のモックを作成できるようにするためにファクトリは必要ありません。インスタンス化してモックを直接渡すだけです」 -繰り返しますが、異なる状況です。ファクトリを使用してインスタンスリクエストします -消費者はこの対話を制御します。コンストラクターまたはメソッドを介してインスタンスを指定しても機能しません。これは別のシナリオです。
AlexFoxGill

79

Factoryパターンの流行は、「new」キーワードの使用は不適切であり、すべてのコスト(または最小集中化)。これは、単一責任原則(SOLIDの「S」)、および依存関係反転原則(「D」)の非常に厳密な解釈に由来しています。簡単に言えば、SRPは理想的にはコードオブジェクトには「変更する理由」が1つだけあるべきだと言っています。「変更の理由」がそのオブジェクトの中心的な目的であり、コードベースでの「責任」であり、コードの変更を必要とするものはすべて、そのクラスファイルを開く必要はありません。DIPはさらにシンプルです。コードオブジェクトが別の具体的なオブジェクトに依存することはありません。

適切な例として、「new」とパブリックコンストラクターを使用して、呼び出し元のコードを特定の具象クラスの特定の構築メソッドに結合しています。コードは、クラスMyFooObjectが存在し、文字列とintを受け取るコンストラクタを持っていることを知る必要があります。そのコンストラクターがさらに情報を必要とする場合は、コンストラクターのすべての使用法を更新して、現在記述しているものを含むその情報を渡す必要があるため、渡すために有効なものが必要です。それを取得するために変更する(消費オブジェクトに責任を追加する)。さらに、コードベースでMyFooObjectがBetterFooObjectに置き換えられた場合、古いクラスではなく新しいオブジェクトを作成するために、古いクラスの使用法をすべて変更する必要があります。

したがって、代わりに、MyFooObjectのすべてのコンシューマーは、MyFooObjectを含むクラスを実装する動作を定義する「IFooObject」に直接依存する必要があります。現在、IFooObjectsのコンシューマーは、IFooObjectを構築することはできません(特定の具体的なクラスが必要ないIFooObjectであるという知識がないため)。外部から、状況に応じて正しいIFooObjectを作成する方法を知る責任がある別のオブジェクトによって。このオブジェクトは、この用語では通常ファクトリと呼ばれます。

さて、ここで理論が現実と出会うところです。オブジェクトを常にすべての種類の変更に対して閉じることできません。代表的な例として、IFooObjectはコードベース内の追加のコードオブジェクトになり、コンシューマーまたはIFooObjectsの実装に必要なインターフェイスが変更されるたびに変更する必要があります。これにより、この抽象化を介してオブジェクトが相互にやり取りする方法を変更することに伴う新しいレベルの複雑さが導入されます。さらに、インターフェース自体が新しいインターフェースに置き換えられた場合、コンシューマーはさらに変更する必要があり、さらに深くする必要があります。

優れたコーダーは、デザインを分析し、特定の方法で変更する必要がある可能性が非常に高い場所を見つけて、より寛容になるようにリファクタリングすることで、YAGNI(「あなたは必要ない」)とSOLIDのバランスを取る方法を知っていますその場合、「あなたそれを必要としている」からです。


21
他のすべてのものを読んだ後、特別に、この答えが大好きです。私は、ほとんどすべての(良い)新しいコーダーが、本当に良くなりたいが、物事をシンプルに保ち、やりすぎないことの価値をまだ学んでいないという単純な事実によって、原則について独断的すぎます。
ジャン

1
パブリックコンストラクターのもう1つの問題は、パブリックコンストラクターFooFooインスタンスの作成、または同じパッケージ/アセンブリ内の他のタイプの作成に使用できるように指定するクラスに良い方法がないが、の作成には使用できないことです。他の場所で派生したタイプ。言語/フレームワークがnew式で使用するための別個のコンストラクターを定義できなかった、特にサブタイプコンストラクターからの呼び出しに対して特に説得力のある理由はわかりませんが、その区別をする言語は知りません。
supercat

1
保護されたコンストラクタおよび/または内部コンストラクタはそのようなシグナルになります。このコンストラクターは、サブクラス内または同じアセンブリ内でコードを使用する場合にのみ使用できます。C#には「保護された内部」のキーワードの組み合わせはありません。つまり、アセンブリ内のサブタイプのみが使用できますが、MSILにはその種類の可視性のスコープ識別子があるため、C#仕様を拡張して利用する方法が考えられます。しかし、これは工場の使用とはあまり関係ありません(可視性の制限を使用して工場の使用を強制しない限り)。
キース

2
完璧な答え。「理論と現実が一致する」という部分を正しく理解してください。貨物カルトの賞賛、正当化、実装、そしてそれらを使用して、あなたが説明したのと同じ状況に陥るのに、何千人もの開発者と人間の時間が費やされたことを考えてください。YAGNIに続いて、Iveは工場を導入する必要性を特定しませんでした
Breno Salgado

私は、OOP実装がコンストラクターのオーバーロードを許可しないため、パブリックコンストラクターの使用が事実上禁止されているコードベースでプログラミングしています。これは、それを許可する言語ではすでに小さな問題です。温度を作成するような。華氏も摂氏も山車です。ただし、それらをボックス化して問題を解決できます。
jgmjgm

33

コンストラクタは、短くシンプルなコードが含まれていれば問題ありません。

初期化がいくつかの変数をフィールドに割り当てる以上の場合、ファクトリは意味をなします。利点のいくつかを次に示します。

  • 長くて複雑なコードは、専用クラス(ファクトリー)でより理にかなっています。多数の静的メソッドを呼び出すコンストラクターに同じコードを配置すると、メインクラスが汚染されます。

  • 一部の言語および場合によっては、コンストラクターに例外をスローすることは、バグを引き起こす可能性があるため、本当に悪い考えです。

  • コンストラクターを呼び出すとき、呼び出し元であるユーザーは、作成するインスタンスの正確なタイプを知る必要があります。これは常にそうではありません(aとして、それを供給するためにFeederを構築する必要がありAnimalます;それがa Dogかa かは気にしませんCat)。


2
選択肢は「工場」または「建設業者」だけではありません。A Feederはどちらも使用せず、代わりにKennelオブジェクトのgetHungryAnimalメソッドを呼び出します。
ダグ

4
+1コンストラクターにロジックがまったくないというルールを守ることは悪い考えではないことがわかりました。コンストラクターは、引数値をインスタンス変数に割り当てることにより、オブジェクトの初期状態を設定するためにのみ使用する必要があります。より複雑なものが必要な場合は、少なくともインスタンスをビルドするファクトリー(クラス)メソッドを作成します。
KaptajnKold

これは私がここで見た唯一の満足のいく答えであり、自分で書く必要がなくなりました。他の答えは、抽象的な概念のみを扱っています。
TheCatWhisperer

しかし、この議論Builder Patternも同様に有効であると考えることができます。じゃない?
soufrk

コンストラクタールール
ブレノサルガド

10

インターフェイスを操作する場合、実際の実装から独立したままでかまいません。多くの異なる実装の1つをインスタンス化するように、ファクトリーを構成できます(プロパティー、パラメーター、またはその他の方法を使用)。

簡単な例:デバイスと通信したいが、それがイーサネット、COM、USBのいずれを経由するかわからない場合。1つのインターフェースと3つの実装を定義します。実行時に、必要なメソッドを選択すると、ファクトリが適切な実装を提供します。

頻繁に使用する...


5
インターフェースの実装が複数あり、呼び出し元のコードがどちらを選択するかわからない場合、または使用すべきでない場合に使用すると便利です。しかし、ファクトリメソッドが質問のように単一のコンストラクターの単なる静的ラッパーである場合、それはアンチパターンです。そこにする必要があり選ぶことや工場が邪魔になって、不必要な複雑さを追加して、そこから複数の実装。

これで柔軟性が向上し、理論上、アプリケーションはイーサネット、COM、USB、シリアルを使用でき、理論上はファイアループなどに対応できます。実際には、アプリはイーサネット経由でのみ通信します。
ピーターB

7

これは、Java / C#のモジュールシステムの制限の症状です。

原則として、同じコンストラクターとメソッドシグネチャを持つクラスの実装を別の実装に交換できない理由はありません。これを可能にする言語があります。ただし、JavaおよびC#では、すべてのクラスに一意の識別子(完全修飾名)が必要であり、クライアントコードには最終的に依存関係がハードコードされます。

あなたはできる種類のものがように、ファイルシステムおよびコンパイラオプションをいじることでこれを回避com.example.Foo別のファイルにマップが、これは驚くべきことであり、直感的です。あなたがそうする場合でも、あなたのコードがされ、まだクラスの唯一の実装に縛ら。すなわち、クラスFooに依存するクラスを記述する場合、コンパイル時にのMySet実装を選択できますが、の2つの異なる実装を使用してをMySetインスタンス化することはできません。FooMySet

この残念な設計上の決定により、人々はinterfacesを不必要に使用して、後で何か別の実装が必要になる可能性や、単体テストを容易にするためにコードを将来的に保証します。これは常に実行可能ではありません。クラスの2つのインスタンスのプライベートフィールドを参照するメソッドがある場合、それらをインターフェイスに実装することはできません。そのため、たとえば、unionJavaのSetインターフェースには表示されません。それでも、数値型とコレクション以外では、バイナリメソッドは一般的ではないため、通常はこれで十分です。

もちろん、呼び出すnew Foo(...)場合はまだclassに依存しているため、クラスでインターフェイスを直接インスタンス化できるようにするにはファクトリが必要です。ただし、通常はコンストラクターでインスタンスを受け入れ、使用する実装を他の人に決定させる方が良いでしょう。

インターフェイスとファクトリーでコードベースを肥大化する価値があるかどうかを判断するのはあなた次第です。一方、問題のクラスがコードベースの内部にある場合、将来別のクラスまたはインターフェイスを使用するようにコードをリファクタリングするのは簡単です。状況が発生した場合は、YAGNIを呼び出して後でリファクタリングできます。ただし、クラスが公開したライブラリのパブリックAPIの一部である場合、クライアントコードを修正するオプションはありません。を使用せずinterface、後で複数の実装が必要な場合は、岩と難しい場所の間で立ち往生します。


2
Javaと.NETのような派生物がそれ自体のインスタンスを作成するクラスの特別な構文を持ち、そうでなければnew特別な名前の静的メソッドを呼び出すための単純なシュガー(タイプに「パブリックコンストラクターがある場合に自動生成される」 「ただし、メソッドを明示的に含めなかった)。私見では、コードが単にimplementsの退屈なデフォルトのものをList必要とする場合、クライアントが特定の実装(例ArrayList)を知らなくてもインターフェースがそれを提供できるようにする必要があります。
supercat

2

私の意見では、彼らは単純なファクトリを使用しているだけです。これは適切なデザインパターンではなく、抽象ファクトリやファクトリメソッドと混同しないでください。

そして、彼らは「何千ものCreateXXX静的メソッドを備えた巨大なファブリッククラス」を作成したので、それはアンチパターンに聞こえます(多分、神のクラスでしょうか?)。

場合によっては、Simple Factoryおよび静的作成メソッド(外部クラスを必要としない)が役立つと思います。たとえば、オブジェクトの構築に、他のオブジェクトのインスタンス化などのさまざまな手順が必要な場合(たとえば、合成を優先する場合)。

私はそれをファクトリーと呼ぶことさえしませんが、接尾辞「Factory」を持つランダムなクラスにカプセル化されたメソッドの束だけです。


1
Simple Factoryにはその場所があります。エンティティが2つのコンストラクターパラメーターとint xを受け取るとしIFooService fooServiceます。fooServiceどこにでも行き渡りたくないので、メソッドCreate(int x)を持つファクトリを作成し、ファクトリ内にサービスを注入します。
AlexFoxGill

4
@AlexGそして、あなたはのIFactory代わりにどこでも渡さなければなりませんIFooService
陶酔

3
私は陶酔に同意します。私の経験では、上からグラフに挿入されるオブジェクトは、すべての下位レベルのオブジェクトが必要とするタイプになる傾向があるため、IFooServicesを渡すことは大したことではありません。ある抽象概念を別の抽象概念に置き換えることは、コードベースをさらに難読化すること以外は何も達成しません。
キース

これは、「オブジェクトを直接構築するのではなくファクトリクラスを使用する必要があるのはなぜですか?いつパブリックコンストラクターを使用する必要があり、いつファクトリを使用する必要があるか」という質問とはまったく無関係です。参照してください。回答する方法
ブヨ

1
それは質問のタイトルに過ぎません、あなたはそれの残りを逃したと思います。リンクの最後のポイントを参照してください;)。
-FranMowinckel

1

ライブラリのユーザーとして、ライブラリにファクトリメソッドがある場合は、それらを使用する必要があります。ファクトリメソッドは、ライブラリの作成者に、コードに影響を与えずに特定の変更を行う柔軟性を提供すると仮定します。たとえば、ファクトリメソッドでサブクラスのインスタンスを返す場合がありますが、単純なコンストラクターでは機能しません。

ライブラリの作成者として、自分でその柔軟性を使用する場合は、ファクトリメソッドを使用します。

あなたが説明する場合、コンストラクタをファクトリメソッドに置き換えることは無意味であるという印象を持っているようです。関係者全員にとって確かに苦痛でした。非常に正当な理由がない限り、ライブラリはAPIから何も削除すべきではありません。そのため、ファクトリメソッドを追加した場合、ファクトリメソッドがそのコンストラクタを呼び出さず、プレーンコンストラクタを使用したコードの動作が本来よりも劣るまで、既存のコンストラクタを使用可能にしたまま、おそらく非推奨にしました。あなたの印象は非常に正しいかもしれません。


また注意してください; 開発者が派生クラスに追加機能(追加プロパティ、初期化)を提供する必要がある場合、開発者はそれを行うことができます。(ファクトリーメソッドがオーバーライド可能であると仮定)。APIの作成者は、特別な顧客のニーズに対処するための回避策を提供することもあります。
エリックシュナイダー

-4

これはScalaと関数型プログラミングの時代には時代遅れになったようです。機能の強固な基盤は、膨大な数のクラスに取って代わります。

また{{、ファクトリを使用する場合、Javaのdouble はもう機能しません。

someFunction(new someObject() {{
    setSomeParam(...);
    etc..
}})

これにより、匿名クラスを作成してカスタマイズできます。

時間空間のジレンマでは、高速CPUのおかげで時間係数が非常に小さくなり、機能プログラミングによりスペースの縮小が可能になり、コードサイズが実用的になりました。


2
これは接線のコメントのように見え(回答方法を参照)、以前の9つの回答で作成され説明されたポイントに対して実質的なものは何も提供していないようです
-gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.