個人的には、オブジェクトを直接インスタンス化する方がはるかに便利なように思われるため、ファクトリクラスの概念を理解したことはありません。私の質問は単純です。どのような状況でファクトリクラスパターンを使用するのが最良のオプションであり、どのような理由で、そして優れたファクトリクラスはどのように見えるのでしょうか。
7 に答える
これが私のコードベースからの実際のライブファクトリーです。一部のデータセットからデータをサンプリングする方法を知っているサンプラー クラスを生成するために使用されます (元は C# にあるため、Java の失敗を許してください)。
class SamplerFactory
{
private static Hashtable<SamplingType, ISampler> samplers;
static
{
samplers = new Hashtable<SamplingType, ISampler>();
samplers.put(SamplingType.Scalar, new ScalarSampler());
samplers.put(SamplingType.Vector, new VectorSampler());
samplers.put(SamplingType.Array, new ArraySampler());
}
public static ISampler GetSampler(SamplingType samplingType)
{
if (!samplers.containsKey(samplingType))
throw new IllegalArgumentException("Invalid sampling type or sampler not initialized");
return samplers.get(samplingType);
}
}
使用例は次のとおりです。
ISampler sampler = SamplerFactory.GetSampler(SamplingType.Array);
dataSet = sampler.Sample(dataSet);
ご覧のとおり、これは大したコードではありません。
ArraySampler sampler = new ArraySampler();
dataSet = sampler.Sample(dataSet);
工場を使用するよりも。では、なぜ私は気にするのですか?基本的な理由が 2 つあります。これらは相互に関係しています。
まず、コードの単純さと保守性です。
enum
呼び出しコードで、がパラメーターとして提供されているとしましょう。つまり、サンプリングを含むデータを処理する必要があるメソッドがある場合、次のように書くことができます。void ProcessData(Object dataSet, SamplingType sampling) { //do something with data ISampler sampler = SamplerFactory.GetSampler(sampling); dataSet= sampler.Sample(dataSet); //do something other with data }
次のような、より面倒な構造の代わりに:
void ProcessData(Object dataSet, SamplingType sampling) { //do something with data ISampler sampler; switch (sampling) { case SamplingType.Scalar: sampler= new ScalarSampler(); break; case SamplingType.Vector: sampler= new VectorSampler(); break; case SamplingType.Array: sampler= new ArraySampler(); break; default: throw new IllegalArgumentException("Invalid sampling type"); } dataSet= sampler.Sample(dataSet); //do something other with data }
この怪物は、サンプリングが必要になるたびに書き込まれる必要があることに注意してください。そして、たとえば、コンストラクターにパラメーターを追加し
ScalarSampler
たり、新しいSamplingType
. そして、このファクトリには現在 3 つのオプションしかありません。20 の実装を持つファクトリを想像してみてください。2 つ目は、コードの分離です。
ArraySampler
ファクトリを使用する場合、呼び出し元のコードは、呼び出されたクラスが存在することさえ知らないか、または知る必要がありません。クラスは実行時に解決することもできますが、呼び出しサイトは賢明ではありません。したがって、たとえば、配列データにも使用ArraySampler
する必要があると判断した場合は、クラスを完全に削除するなど、必要に応じてクラスを自由に変更できます。ScalarSampler
行を変更するだけで済みますsamplers.put(SamplingType.Array, new ArraySampler());
に
samplers.put(SamplingType.Array, new ScalarSampler());
そしてそれは魔法のように機能します。何百もの数になる可能性のある、呼び出し元のクラスのコードを 1 行も変更する必要はありません。事実上、ファクトリにより、サンプリングの内容と方法を制御でき、サンプリングの変更は、システムの残りの部分とインターフェイスする単一のファクトリ クラス内に効率的にカプセル化されます。
ここでの考え方は、関心の分離です。オブジェクトを使用するコードにもインスタンス化するのに十分な情報がある場合、ファクトリは必要ありません。ただし、API ユーザーに考えさせたくない (または混乱させたくない) ロジックまたは構成が含まれている場合は、ファクトリでそれらすべてを非表示にする (および再利用のためにカプセル化する) ことができます。
以下に例を示します。Google App Engine が提供するサービスの 1 つにアクセスしたいとします。同じコードが、開発環境 (マスター/スレーブと高可用性の 2 つのバージョンがあります) と、まったく異なるローカル開発環境の両方で機能するはずです。Google は内部インフラストラクチャの内部の仕組みについて教えたくないので、あなたも知りたくないでしょう。つまり、彼らが行うことは、インターフェイスとファクトリ (および、知る必要さえないファクトリが選択できるこれらのインターフェイスのいくつかの実装) を提供することです。
個人的には、インターフェイスの実装が実行時に不明な場合、または動的にできる場合は、ファクトリ パターンを使用します。
これは、開発者として、オブジェクトのインスタンスへの既知のインターフェイスに対して作業することを意味しますが、実装がどのように機能するかには関心がありません。
例を挙げてみましょう。ファクトリ パターンを使用して、データベースからオブジェクトを提供できます。そのデータベースがフラット ファイル、ローカル/シングル ユーザー データベース、サーバー データベース、Web リソースのいずれであっても、ファクトリがそれらのオブジェクトを生成および管理できることだけは気にしません。
これらのケースごとに実装を書かなければならないのは嫌です:P
Joshua Bloch によるEffective Java book から、私が部分的に書き直しました:
1)コンストラクタとは異なり、静的ファクトリ メソッド ( SFM ) には名前があります。
public static ComplexNumber one () {
return new ComplexNumber(1, 0);
}
public static ComplexNumber imgOne () {
return new ComplexNumber(0, 1);
}
public static ComplexNumber zero () {
return new ComplexNumber(0, 0);
}
SFM
2)呼び出されるたびに新しいオブジェクトを作成する必要はありません
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
3)SFM
戻り値の型の任意のサブタイプのオブジェクトを返すことができます。
4)SFM
パラメータ化された型のインスタンスを作成する冗長性を減らします。
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();
Thilo の答えを補完するために、コンストラクターとしてブール値しか持たないオブジェクトがあるとします。可能な値が 2 つしかないため、毎回 1 つを構築するのは無駄です。
この場合、静的ファクトリ メソッドを作成できます。Java のBoolean
クラスは例です: Boolean.valueOf()
.
ウィキペディアを参照することもできますが、ほとんどの設計パターンの基本的な考え方は、保守性や再利用性を向上させるために何らかの抽象化を導入することです。ファクトリ メソッド パターンも例外ではなく、作成の複雑さをコードから抽象化します。
単純なケースでは、ファクトリ パターンを使用する必要がないように思われます。単純に anew
で十分です。ただし、より高い柔軟性や機能が必要な場合は、このパターンが役立つ場合があります。
たとえば、新しいインスタンスが必要でない限り、不要なオブジェクトの作成を回避するため、静的ファクトリvalueOf(boolean)
は通常、 よりも適切な選択です。ファクトリ メソッド パターンは、仮想コンストラクターnew Bealean(boolean)
とも呼ばれます。ご存知のように、ポリモーフィズムは OOP の重要な機能の 1 つですが、コンストラクターをポリモーフィックにすることはできません。この欠点は、ファクトリ メソッド パターンによって克服できます。
本質的に、オブジェクトを直接 (通常は を介してnew
) インスタンス化することは、ほとんど具体的な実装ではありませんが、ファクトリ メソッド パターンは、安定したインターフェイス(Java に限定されない)によって揮発性の 実装を保護し、オブジェクト作成のロジックをいくつかの抽象化の背後に押して確実にします。より保守しやすく再利用可能なコード。 interface
最後に、ファクトリ メソッド パターンやその他の設計パターンの利点を完全に理解するには、データの抽象化、ポリモーフィックな抽象化、SOLIDの原則など、OOP の本質を理解する必要があります。
工場自体は、そう簡単にはその美しさを発揮しません。たとえば、デコレーター パターンを使用する場合、オブジェクトを直接インスタンス化すると、コードに追加の結合が追加される場合があります。OOP の先生が言うように、カップリングは悪い :) したがって、装飾されたオブジェクトをインスタンス化し、カップリングを増やしたくない場合は、ファクトリを使用できます。