ファクトリー・パターンのアイデアはわかりましたが、このパターンを使用する必要は本当にないと思います。
たとえば、以下は、ファクトリーメソッドを使用するコード(C#)です。
public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("Dog says: Bow-Wow.");
}
}
public class Tiger : IAnimal
{
public void Speak()
{
Console.WriteLine("Tiger says: Halum.");
}
}
public abstract class IAnimalFactory
{
public abstract IAnimal CreateAnimal();
}
public class TigerFactory : IAnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Tiger();
}
}
public class DogFactory : IAnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Dog();
}
}
クライアントは以下を呼び出すことができます:
IAnimalFactory tigerFactory = new TigerFactory();
IAnimal aTiger = tigerFactory.MakeAnimal();
aTiger.Speak(); //3 lines of code, plus needing of extra factory classes
しかし、クライアントは次のようにすることもできます:
IAnimal aTiger = new Tiger();
aTiger.Speak(); //only 2 lines of code
2行のコードが必要であり、ファクトリクラスを定義する必要がないことがわかります。では、なぜ工場を定義して使用するために追加の手順を実行するのでしょうか。
Ant PはRandomNumberOfAnimalsGeneratorにファクトリが必要であると返信しましたが、以下は私のバージョンのクラスですが、まだファクトリは必要ありません。
public class RandomNumberOfAnimalsGenerator
{
private readonly animal ;
public RandomNumberOfAnimalsGenerator(IAnimal animal)
{
this.animal = animal;
}
public List<IAnimal> GetAnimals()
{
var animals = new List<IAnimal>();
var n = RandomNumber();
for(int i=0; i<n; i++)
{
animals.Add(animal);
}
return animals;
}
}
そしてクライアントが呼び出す:
var RandomNumberOfAnimalsGenerator = new RandomNumberOfAnimalsGenerator(new Tiger());
まだ工場は必要ありません
ここでさらに別の答えが必要かどうかはわかりませんが、ファクトリーの目的を説明するためにコードを変更するように依頼されました。したがって、おそらく以下が役立ちます。
まず、あなたのIAnimalFactory
を抽象クラスではなくインターフェースに変更します。それは後者であるという有用な目的を果たしません。また、特定の状況でどのような種類の動物が欲しいかについて、いくつかの基準を設ける可能性があります。それでは、その基準を定義するenum
を用意しましょう:
public enum AnimalType
{
Pet,
Wild
}
次に、ファクトリインターフェースを作成します。
public interface IAnimalFactory
{
IAnimal CreateAnimal(AnimalType typeofAnimal);
}
これで、動物の存在について何も知る必要なく、コードの他の部分で動物を作成できるようになりました。ワイルドなものを必要とするかどうかを指定するだけです:
public void CreateAWildAnimalAndMakeItTalk(IAnimalFactory factory)
{
var animal = factory.CreateAnimal(AnimalType.Wild);
animal.Speak();
}
あとは、実際のファクトリを作成するだけです。シンプルにしましょう:
internal class SimpleAnimalFactory : IAnimalFactory
{
public IAnimal CreateAnimal(AnimalType typeofAnimal)
{
return typeofAnimal == AnimalType.Wild ? new Tiger() : new Dog();
}
}
そして、それはコードの他の場所で使用されます:
CreateAWildAnimalAndMakeItTalk(new SimpleAnimalFactory());
しかし、犬も野生になる可能性があるため、ファクトリーの別の実装を提供したい場合があります。
internal class RandomWildAnimalFactory : IAnimalFactory
{
private Random _random = new Random();
public IAnimal CreateAnimal(AnimalType typeofAnimal)
{
if (typeofAnimal == AnimalType.Pet) return new Dog();
return _random.Next(2) == 0 ? new Tiger() : new Dog();
}
}
次に、他のコードを変更せずに、新しいファクトリでメソッドを呼び出すことができます。
CreateAWildAnimalAndMakeItTalk(new RandomWildAnimalFactory());
ファクトリメソッドには多くの利点があります。主に、それらはコンストラクターの組み込みの制限を回避します(名前を1つしか持てない、キャッシングを使用できない、など)。
Entire Factoriesは主に、インターフェイスのインスタンス化に使用される具象型からクライアントコードを分離するために使用されます。しかし、あなたの例はこれをしません。クライアントはTiger
タイプを知っている必要はありませんが、代わりにTigerFactory
タイプを知っている必要があります。これは改善されていません。
私の見解では、これはカーゴカルトプログラミングです-誰かが工場を使うように言われ、工場のポイントが何であるかを理解せずに従いました。適切なことは、虎と犬の両方をインスタンス化できるAnimalFactory
を用意することでしたが、いつ提供するかを決定できる場合のみです。それ以外の場合は、使用する動物の具体的なタイプに関する決定はクライアントに委ねる必要があります。
Factory Methodパターンの利点は、コードの一部がIAnimal
の取得方法を知りたくない、または知りたくないという事実を表現することです。
実際、インターフェースはしばしば間違った視点から見られます。クライアントコードに強制的にインターフェースを使用させることはできません。
むしろ、クライアントコードはspecifyにインターフェイスが必要であることを示します。この場合は、「動物を作成する方法」です。
クライアントコードがdefinedインターフェース自体であっても、「あなたは作成を大事にし、私はそれらを動物園に入れます」と表現するほど、馬鹿げたことはないでしょう。
このパターンは、プログラミング言語にラムダ式がなかった時代に定義されたことに注意してください。今日のコンテキストでは、パターンの多くが古くなっています。 Factoryメソッドは、単純なFunc<IAnimal>
で実装できるようになりました。
public void myCode(Func<IAnimal> createAnimal) {
var a = createAnimal();
... // stuff with a
}
さまざまなユースケースに対応するさまざまなタイプのファクトリーがあります。多くの場合、ランタイム引数に応じて異なる(サブ)クラスのオブジェクトを作成します。この場合、単純な関数またはポリモーフィックでないクラスで十分です。
public class AnimalFactory
{
public static IAnimal Create(String typeName)
{
if (typeName == "Tiger")
{
return new Tiger();
}
if (typeName == "Dog")
{
return new Dog();
}
// handle error, for example:
throw Exception("Unkown type name: " + typeName);
}
}
このファクトリーを使用すると、例えば、ユーザー入力に応じて異なる動物を作成できます。
String userInput = Console.ReadLine();
IAnimal animal = AnimalFactory.Create(userInput);
animal.Speak();
すでにお気づきのとおり、異なるファクトリサブクラスを持つ基本クラス/インターフェイスIAnimalFactory
は必要ありません。ここでは、それらは工場での使用を複雑にするだけです。
上記のファクトリーでは、Create
を呼び出すたびに、型の選択を可能にする引数userInput
を提供する必要があります。ただし、場合によっては、タイプの選択を実際の作成から分離する必要があります。次の例を見てください。
public class AnimalStore
{
IAnimalFactory m_animalFactory;
double m_price;
public AnimalStore(IAnimalFactory animalFactory, double price)
{
this.m_animalFactory = animalFactory;
this.m_price = price;
}
public void SellAnimalToUser(User user)
{
if (user.money < this.m_price)
{
throw Exception("Not enough money");
}
user.money -= this.m_price;
user.AddAnimal(m_animalFactory.CreateAnimal());
}
}
AnimalStore
はポリモーフィックではありません。ただし、別の店でも別の動物を作成できます。 SellAnimalToUser
(Create
を呼び出す)を呼び出す場合、正しい動物タイプを選択する引数は必要ありません。
AnimalStore tigerStore = new AnimalStore(new TigerFactory(), 1000.);
AnimalStore dogStore = new AnimalStore(new DogFactory(), 200.);
User peter = new User();
peter.money = 5000;
tigerStore.SellAnimalToUser(peter);
dogStore.SellAnimalToUser(peter);
タイプの選択とオブジェクトの作成を分離する問題は、他の方法でも解決できます。たとえば、AnimalFactory
関数の代わりにコンストラクタでString typeName
を指定できる、非ポリモーフィックCreate
を使用できます。または、工場の代わりにファンクタを注入することもできます。これらの方法にはそれぞれ長所と短所があり、どの方法を使用するかは通常、使用方法によって異なります。
なぜ工場が必要になるのかという例を尋ねましたが、まだ工場が与えられていません。
ランダムな数の動物を作成するコードがあるとします。これを特定の種類の動物を作成する方法の詳細からどのように分離しますか?
答えは工場です。
public class RandomNumberOfAnimalsGenerator
{
private readonly Func<IAnimal> createAnimal;
public RandomNumberOfAnimalsGenerator(Func<IAnimal> createAnimal)
{
this.createAnimal = createAnimal;
}
public List<IAnimal> GetAnimals()
{
var animals = new List<IAnimal>();
var n = RandomNumber();
for(int i=0; i<n; i++)
{
animals.Add(createAnimal());
}
return animals;
}
}
これがないとfactoryで、RandomNumberOfAnimalsGenerator
はあらゆる種類の動物を作成する方法を知る必要があります。
これらの回答で私が見たことのない一般的な使用法の1つは、問題を逆に見ているようなものです(サンプルコードはファクトリーを消費し、作成するため、一般的には役に立ちません)。これは、相互作用するコードの2つ以上のセット(完全に異なるチームによって維持されている)がある場合に適しています。
例として、ログライブラリのようなものを考えてみましょう...これは、ファイルにログアウトするために使用するユーティリティクラスかもしれません。
ログにかなりの数の宛先、ファイル、データベース、コンソールなどがあるとします。これらは、構成コマンドまたは構成ファイルのいずれかを使用して構成できますが、他のデバイス(リモートポート、または奇妙な古いポート)にログを記録する場合はどうでしょうか。クローゼットで見つけた紙テープのパンチ?その上で、同じロギングソフトウェアを使用して特別なロギングバックエンドを使用する可能性がある、使用している他のライブラリが必要です。
つまり、ログを記録するときに実行するコードをロガーにどのように提供できますか?
私は3つの方法を見ることができます:
2と3は近いです。主な違いは、クライアントコードがロガーを作成するたびにライブラリが新しいロギングバックエンドを必要とする場合、ソリューション2は機能せず、ファクトリが必要になることです。
ユーザーの観点から見ると、抽象ファクトリを使用していることすらほとんどわかりませんが、ロギングライブラリの実装者として見ると、かなり必要なようです。