web-dev-qa-db-ja.com

インターフェイスを使用するだけで十分な場合に、抽象ファクトリを使用する利点は何ですか?

ファクトリー・パターンのアイデアはわかりましたが、このパターンを使用する必要は本当にないと思います。

たとえば、以下は、ファクトリーメソッドを使用するコード(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());

まだ工場は必要ありません

3
secondimage

ここでさらに別の答えが必要かどうかはわかりませんが、ファクトリーの目的を説明するためにコードを変更するように依頼されました。したがって、おそらく以下が役立ちます。

まず、あなたの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());
8
David Arno

ファクトリメソッドには多くの利点があります。主に、それらはコンストラクターの組み込みの制限を回避します(名前を1つしか持てない、キャッシングを使用できない、など)。

Entire Factoriesは主に、インターフェイスのインスタンス化に使用される具象型からクライアントコードを分離するために使用されます。しかし、あなたの例はこれをしません。クライアントはTigerタイプを知っている必要はありませんが、代わりにTigerFactoryタイプを知っている必要があります。これは改善されていません。

私の見解では、これはカーゴカルトプログラミングです-誰かが工場を使うように言われ、工場のポイントが何であるかを理解せずに従いました。適切なことは、虎と犬の両方をインスタンス化できるAnimalFactoryを用意することでしたが、いつ提供するかを決定できる場合のみです。それ以外の場合は、使用する動物の具体的なタイプに関する決定はクライアントに委ねる必要があります。

7
Kilian Foth

Factory Methodパターンの利点は、コードの一部がIAnimalの取得方法を知りたくない、または知りたくないという事実を表現することです。

実際、インターフェースはしばしば間違った視点から見られます。クライアントコードに強制的にインターフェースを使用させることはできません。

むしろ、クライアントコードはspecifyにインターフェイスが必要であることを示します。この場合は、「動物を作成する方法」です。

クライアントコードがdefinedインターフェース自体であっても、「あなたは作成を大事にし、私はそれらを動物園に入れます」と表現するほど、馬鹿げたことはないでしょう。

このパターンは、プログラミング言語にラムダ式がなかった時代に定義されたことに注意してください。今日のコンテキストでは、パターンの多くが古くなっています。 Factoryメソッドは、単純なFunc<IAnimal>で実装できるようになりました。

public void myCode(Func<IAnimal> createAnimal) {
   var a = createAnimal();
   ... // stuff with a
}
4
xtofl

さまざまなユースケースに対応するさまざまなタイプのファクトリーがあります。多くの場合、ランタイム引数に応じて異なる(サブ)クラスのオブジェクトを作成します。この場合、単純な関数またはポリモーフィックでないクラスで十分です。

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はポリモーフィックではありません。ただし、別の店でも別の動物を作成できます。 SellAnimalToUserCreateを呼び出す)を呼び出す場合、正しい動物タイプを選択する引数は必要ありません。

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を使用できます。または、工場の代わりにファンクタを注入することもできます。これらの方法にはそれぞれ長所と短所があり、どの方法を使用するかは通常、使用方法によって異なります。

4
pschill

なぜ工場が必要になるのかという例を尋ねましたが、まだ工場が与えられていません。

ランダムな数の動物を作成するコードがあるとします。これを特定の種類の動物を作成する方法の詳細からどのように分離しますか?

答えは工場です。

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はあらゆる種類の動物を作成する方法を知る必要があります。

0
Ant P

これらの回答で私が見たことのない一般的な使用法の1つは、問題を逆に見ているようなものです(サンプルコードはファクトリーを消費し、作成するため、一般的には役に立ちません)。これは、相互作用するコードの2つ以上のセット(完全に異なるチームによって維持されている)がある場合に適しています。

例として、ログライブラリのようなものを考えてみましょう...これは、ファイルにログアウトするために使用するユーティリティクラスかもしれません。

ログにかなりの数の宛先、ファイル、データベース、コンソールなどがあるとします。これらは、構成コマンドまたは構成ファイルのいずれかを使用して構成できますが、他のデバイス(リモートポート、または奇妙な古いポート)にログを記録する場合はどうでしょうか。クローゼットで見つけた紙テープのパンチ?その上で、同じロギングソフトウェアを使用して特別なロギングバックエンドを使用する可能性がある、使用している他のライブラリが必要です。

つまり、ログを記録するときに実行するコードをロガーにどのように提供できますか?

私は3つの方法を見ることができます:

  1. ロギングライブラリの設定ファイルにクラス名を追加します
  2. ロガーにロギングバックエンドの具体的なインスタンスを提供する
  3. ロギングライブラリにファクトリを提供し、ロギングバックエンドの独自のインスタンスを作成するために使用できるようにします。

2と3は近いです。主な違いは、クライアントコードがロガーを作成するたびにライブラリが新しいロギングバックエンドを必要とする場合、ソリューション2は機能せず、ファクトリが必要になることです。

ユーザーの観点から見ると、抽象ファクトリを使用していることすらほとんどわかりませんが、ロギングライブラリの実装者として見ると、かなり必要なようです。

0
Bill K