web-dev-qa-db-ja.com

DIおよびIoCを使用したファクトリメソッド

私はこれらのパターンに精通していますが、まだ次の状況を処理する方法がわかりません。

public class CarFactory
{
     public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6)
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(Dep1,Dep2,Dep3);
               break;

               case B:
                   return new Car2(Dep4,Dep5,Dep6);
               break;
            }
     }
}

一般に、問題は注入する必要がある参照の量にあります。車がもっと多くなるとさらに悪化します。

私の頭に浮かぶ最初のアプローチは、Car1とCar2をファクトリコンストラクタに注入することですが、ファクトリは常に同じオブジェクトを返すため、ファクトリアプローチに反します。 2番目のアプローチはservicelocatorを挿入することですが、それはどこでもアンチパターンです。解決方法

編集:

代替方法1:

public class CarFactory
{
     public CarFactory(IContainer container)
     {
        _container = container;
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return _container.Resolve<ICar1>();
               break;

               case B:
                     return _container.Resolve<ICar2>();
               break;
            }
     }
}

代替方法2(ツリー内の依存関係が多すぎるため使用が困難):

public class CarFactory
{
     public CarFactory()
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....)
               break;

               case B:
                    return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....)
               break;
            }
     }
}
35
MistyK

工場内でswitch caseステートメントを使用することは、コードのにおいです。興味深いことに、あなたはその問題の解決にまったく焦点を合わせていないようです。

このシナリオで最も使いやすいDIソリューションは、 戦略パターン です。これにより、DIコンテナーは、他のクラスをそれらの依存関係で混乱させたり、サービスロケーターに頼ったりすることなく、それらが属するファクトリーインスタンスに依存関係を注入できます。

インターフェース

public interface ICarFactory
{
    ICar CreateCar();
    bool AppliesTo(Type type);
}

public interface ICarStrategy
{
    ICar CreateCar(Type type);
}

工場

public class Car1Factory : ICarFactory
{
    private readonly IDep1 dep1;
    private readonly IDep2 dep2;
    private readonly IDep3 dep3;

    public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3)
    {
        if (dep1 == null)
            throw new ArgumentNullException("dep1");
        if (dep2 == null)
            throw new ArgumentNullException("dep2");
        if (dep3 == null)
            throw new ArgumentNullException("dep3");

        this.dep1 = dep1;
        this.dep2 = dep2;
        this.dep3 = dep3;
    }

    public ICar CreateCar()
    {
        return new Car1(this.dep1, this.dep2, this.dep3);
    }

    public bool AppliesTo(Type type)
    {
        return typeof(Car1).Equals(type);
    }
}

public class Car2Factory : ICarFactory
{
    private readonly IDep4 dep4;
    private readonly IDep5 dep5;
    private readonly IDep6 dep6;

    public Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
    {
        if (dep4 == null)
            throw new ArgumentNullException("dep4");
        if (dep5 == null)
            throw new ArgumentNullException("dep5");
        if (dep6 == null)
            throw new ArgumentNullException("dep6");

        this.dep4 = dep4;
        this.dep5 = dep5;
        this.dep6 = dep6;
    }

    public ICar CreateCar()
    {
        return new Car2(this.dep4, this.dep5, this.dep6);
    }

    public bool AppliesTo(Type type)
    {
        return typeof(Car2).Equals(type);
    }
}

戦略

public class CarStrategy : ICarStrategy
{
    private readonly ICarFactory[] carFactories;

    public CarStrategy(ICarFactory[] carFactories)
    {
        if (carFactories == null)
            throw new ArgumentNullException("carFactories");

        this.carFactories = carFactories;
    }

    public ICar CreateCar(Type type)
    {
        var carFactory = this.carFactories
            .FirstOrDefault(factory => factory.AppliesTo(type));

        if (carFactory == null)
        {
            throw new Exception("type not registered");
        }

        return carFactory.CreateCar();
    }
}

使用法

// I am showing this in code, but you would normally 
// do this with your DI container in your composition 
// root, and the instance would be created by injecting 
// it somewhere.
var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6)
    });

// And then once it is injected, you would simply do this.
// Note that you could use a magic string or some other 
// data type as the parameter if you prefer.
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));

Switch caseステートメントがないため、設計を変更せずに戦略に追加のファクトリーを追加でき、それらの各ファクトリーには、DIコンテナーによって注入される独自の依存関係を持たせることができます。

var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6),
    new Car3Factory(dep7, dep8, dep9)
    });

var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));
var car3 = strategy.CreateCar(typeof(Car3));
66
NightOwl888

Composition Rootを使用して、コード例に関するコメントに答えます。以下を作成できますが、これはサービスロケーターではありません。

public class CarFactory
{
    private readonly Func<Type, ICar> carFactory;

    public CarFactory(Func<Type, ICar> carFactory)
    {
       this.carFactory = carFactory;
    }

    public ICar CreateCar(Type carType)
    {
        return carFactory(carType);
 }

これは、Unity DIコンテナーを使用したComposition Rootの外観です。

Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
7
Arkadiusz K

少し前に同様の質問に答えました。基本的にあなたの選択がすべてです。冗長性(コンパイラーからより多くのヘルプを提供します)と自動化を選択する必要があります。自動化により、コードの記述は少なくなりますが、バグが発生しやすくなります。

これ は、冗長性をサポートする私の答えです。

this も自動化をサポートする良い答えです。

[〜#〜] edit [〜#〜]

あなたが間違っていると考えるアプローチは実際には最高だと思います。真実は言われていますが、通常、そこには多くの依存関係がありませんso。このアプローチは非常に明示的であり、ランタイムエラーが発生することはめったにないため、このアプローチが気に入っています。

代替方法1:

これは悪いです。これは実際にはサービスロケーターであり、 アンチパターン と見なされます。

代替方法2

あなたが書いたように、IOCコンテナと混合した場合、使用するのは簡単ではありません。しかし、場合によっては同様のアプローチ( poor man's DI )が有用です。

全体として、私はあなたの工場で「多くの」依存関係を持つことを気にしません。これは、単純な宣言型コードです。書くのに数秒かかり、実行時エラーに苦労する時間を節約できます。

2
Andrzej Gis

まず、具体的な工場があります。IoCコンテナーは、そこに役立つものではなく、代わりになる可能性があります。

次に、ファクトリをリファクタリングして、ファクトリコンストラクタで可能な完全なパラメータリストを期待しないようにします。これが主要な問題です-ファクトリメソッドがそれらを必要としないのに、なぜそんなに多くのパラメーターを渡すのですか?

私はむしろ特定のパラメーターをファクトリーメソッドに渡します

public abstract class CarFactoryParams { }

public class Car1FactoryParams : CarFactoryParams
{
   public Car1FactoryParams(Dep1, Dep2, Dep3) 
   { 
      this.Dep1 = Dep1;
      ...
}

public class Car2FactoryParams 
      ...

public class CarFactory
{
    public ICar CreateCar( CarFactoryParams params )
    {
        if ( params is Car1FactoryParams )
        {
            var cp = (Car1FactoryParams)params;
            return new Car1( cp.Dep1, cp.Dep2, ... );
        }
        ...
        if ( params is ...

パラメータリストを特定のクラスにカプセル化することにより、特定のファクトリメソッドの呼び出しに必要なこれらのパラメータをクライアントに正確に提供させるだけです。

編集:

残念ながら、あなたの投稿からこれらが何であるかは明確ではありませんでしたDep1、...およびそれらの使用方法。

次に、ファクトリプロバイダーを実際のファクトリ実装から分離する次のアプローチをお勧めします。このアプローチはLocal Factoryパターンとして知られています:

public class CarFactory
{
   private static Func<type, ICar> _provider;

   public static void SetProvider( Func<type, ICar> provider )
   {
     _provider = provider;
   }

   public ICar CreateCar(type)
   {
     return _provider( type );
   }
}

ファクトリー自体には実装はありません。ドメインAPIの基盤を設定するためにここにあり、このAPIでのみ車のインスタンスを作成する必要があります。

次に、コンポジションルート(実際のコンテナーを構成するアプリの開始点の近く)で、プロバイダーを構成します。

CarFactory.SetProvider(
    type =>
    {
        switch ( type )
        {
           case A:
             return _container.Resolve<ICar1>();
           case B:
             return _container.Resolve<ICar2>();
           ..
    }
);

このファクトリのプロバイダの実装例ではデリゲートを使用していますが、実際のプロバイダの仕様としてインターフェイスを使用することもできます。

この実装は、基本的に編集した質問の#1ですが、特定の欠点はありません。クライアントは引き続き以下を呼び出します。

var car = new CarFactory().CreareCar( type );
1
Wiktor Zychla

Wiktorの答えに似たものを利用できるように、依存関係に適切な構造を与えることを検討しますが、自動車工場自体を抽象化します。次に、if..then構造を使用しません。

public interface ICar
{
    string Make { get; set; }
    string ModelNumber { get; set; }
    IBody Body { get; set; }
    //IEngine Engine { get; set; }
    //More aspects...etc.
}

public interface IBody
{
    //IDoor DoorA { get; set; }
    //IDoor DoorB { get; set; }
    //etc
}

//Group the various specs
public interface IBodySpecs
{
    //int NumberOfDoors { get; set; }
    //int NumberOfWindows { get; set; }
    //string Color { get; set; }
}

public interface ICarSpecs
{
    IBodySpecs BodySpecs { get; set; }
    //IEngineSpecs EngineSpecs { get; set; }
    //etc.
}

public interface ICarFactory<TCar, TCarSpecs>
    where TCar : ICar
    where TCarSpecs : ICarSpecs
{
    //Async cause everything non-trivial should be IMHO!
    Task<TCar> CreateCar(TCarSpecs carSpecs);

    //Instead of having dependencies ctor-injected or method-injected
    //Now, you aren't dealing with complex overloads
    IService1 Service1 { get; set; }
    IBuilder1 Builder1 { get; set; }
}

public class BaseCar : ICar
{
    public string Make { get; set; }
    public string ModelNumber { get; set; }
    public IBody Body { get; set; }
    //public IEngine Engine { get; set; }
}

public class Van : BaseCar
{
    public string VanStyle { get; set; } 
    //etc.
}

public interface IVanSpecs : ICarSpecs
{
    string VanStyle { get; set; }
}

public class VanFactory : ICarFactory<Van, IVanSpecs>
{
    //Since you are talking of such a huge number of dependencies,
    //it may behoove you to properly categorize if they are car or 
    //car factory dependencies
    //These are injected in the factory itself
    public IBuilder1 Builder1 { get; set; }
    public IService1 Service1 { get; set; }

    public async Task<Van> CreateCar(IVanSpecs carSpecs)
    {
        var van = new Van()
        {
           //create the actual implementation here.
        };
        //await something or other
        return van;
    }
}

私はそれをリストしませんでしたが、今では複数のタイプの車とそれに対応する工場を実装し、DIを使用して必要なものを注入できます。

1
user4275029

多くのDIコンテナは、名前付き依存関係の概念をサポートしています。

例えば。 (構造マップの構文)

For<ICar>().Use<CarA>().Named("aCar");
Container.GetNamedInstance("aCar") // gives you a CarA instance

慣習のようなもの、具体的な車のタイプ自体から名前が派生するルールを使用する場合、システムを拡張するときに工場に触れる必要はもうありません。

これを工場で使用するのは簡単です。

class Factory(IContainer c) {
  public ICar GetCar(string name) {
    Return c.GetNamedInstance(name);
  }
}
0
flq