web-dev-qa-db-ja.com

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

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

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

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

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

168
rufanov

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

ファクトリーとインターフェースにより、長期的な柔軟性が大幅に向上します。これにより、より分離された、したがってよりテスト可能な設計が可能になります。以下は、このパスを使用しない理由の完全なリストです。

  • Inversion of Control (IoC)コンテナを簡単に導入できます
  • インターフェイスをモックできるので、コードをテストしやすくなります
  • これにより、アプリケーションを変更する際の柔軟性が大幅に向上します(つまり、依存コードを変更せずに新しい実装を作成できます)。

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

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

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

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

アセンブリ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コンテナーを使用して依存関係を解決すると、さらに柔軟性が高まります。クラスの依存関係を変更するたびに、コンストラクターへの各呼び出しを更新する必要はありません。

インターフェース分離の原則と依存関係の逆転の原則に従うことで、柔軟性の高い分離されたアプリケーションを構築できます。これらのタイプのアプリケーションの1つに取り組んだら、newkeywordの使用に戻る必要はもうありません。

58
Stephen

whatsisnameが言ったように、これは cargo cult ソフトウェア設計の場合だと思います。ファクトリ、特に抽象的な種類は、モジュールがクラスの複数のインスタンスを作成し、このモジュールのユーザーに作成するタイプを指定する機能を提供する場合にのみ使用できます。ほとんどの場合、1つのインスタンスが必要であり、明示的なファクトリを作成する代わりにそのインスタンスを直接渡すことができるため、この要件は実際には非常にまれです。

問題は、ファクトリー(およびシングルトン)は実装が非常に簡単であるため、必要のない場所でも、人々はそれらを頻繁に使用することです。プログラマーが「このコードではどのデザインパターンを使用すればよいか」と考えると、工場が彼の頭に浮かぶ最初のものです。

「たぶん、ある日、これらのクラスを別の方法で作成する必要がある」ことを念頭に置いて、多くの工場が作成されています。 [〜#〜] yagni [〜#〜] の明確な違反です。

また、IoCは単なるファクトリの一種であるため、IoCフレームワークを導入するとファクトリは古くなります。そして、多くのIoCフレームワークは特定のファクトリの実装を作成することができます。

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

131
Euphoric

Factoryパターンの流行は、「C」スタイルの言語(C/C++、C#、Java)のプログラマーの間でほぼ独断的な信念から生じており、「new」キーワードの使用は不適切であり、すべてのコストで(または最小集中型)。これは、単一責任の原則(SOLIDの「S」)および依存関係の逆転の原則(「D」)の超厳密な解釈に由来します。簡単に言うと、SRPは、コードオブジェクトには「変更する理由」が1つと1つだけあるのが理想的であるとしています。その「変更する理由」はそのオブジェクトの中心的な目的であり、コードベースにおける「責任」であり、コードの変更を必要とするその他のことは、そのクラスファイルを開く必要はありません。 DIPはさらに簡単です。コードオブジェクトは、別の具象オブジェクトに依存するのではなく、抽象化に依存する必要があります。

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

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

ここで、理論と現実が出会います。オブジェクトは常にneverですべてのタイプの変更をクローズできます。適切な例として、IFooObjectはコードベースの追加のコードオブジェクトになり、コンシューマーまたはIFooObjectsの実装に必要なインターフェイスが変更されるたびに変更する必要があります。これにより、この抽象化を介してオブジェクトが相互に対話する方法を変更することに関連する新しいレベルの複雑さが導入されます。さらに、インターフェース自体が新しいものに置き換えられた場合、消費者は依然として、さらに深く変更する必要があります。

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

81
KeithS

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

初期化がいくつかの変数をフィールドに割り当てる以上のものになると、ファクトリーは理にかなっています。ここにいくつかの利点があります:

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

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

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

34

インターフェースを使用する場合、実際の実装から独立したままにすることができます。ファクトリは、(プロパティ、パラメータ、またはその他のメソッドを介して)構成して、さまざまな実装の1つをインスタンス化できます。

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

よく使う...

11
paul

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

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

_com.example.Foo_が別のファイルにマップされるように、ファイルシステムとコンパイラオプションをいじることで、ソートしてこれを回避できますが、これは意外で直感的ではありません。その場合でも、コードはstillであり、クラスの1つの実装にのみ関連付けられています。つまりクラスFooに依存するクラスMySetを作成する場合、コンパイル時にMySetの実装を選択できますが、Fooの2つの異なる実装を使用してMySetsをインスタンス化することはできません。

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

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

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

7
Doval

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

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

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

私はそれをFactoryと呼ぶことすらしませんが、接尾辞 "Factory"を持ついくつかのランダムなクラスにカプセル化された一連のメソッドだけです。

2
FranMowinckel

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

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

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

1
gnasher729