5

依存性注入の実装について、ある具体的な例では混乱しています。DIコンテナを介して注入される抽象ファクトリでの作業

たとえば、IClassX型の依存関係を持つSomeClassクラスがあるとします。 IClassXインターフェースの具体的な実装の

public class SomeClass 
{ 
    public SomeClass(IClassX dependency){...} 
} 

作成は、私がどのような実装を知らないので、私は、(Unityが使用されている)DIコンテナを設定することはできません特定のコンストラクタで、ランタイム・パラメータN

に依存しますIClassXは実行時に使用されます。 彼の本「依存性注入」のMark Seemannは、注入パラメータとして抽象ファクトリを使用すべきであることを示唆しています。

ランタイムパラメータのrunTimeParamに基づいてIClassXの実装を返すSomeAbstractFactoryがあります。

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(){ } 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(); 
      case 2: return new ClassX2(); 
       default : return new ClassDefault(); 
     } 
    } 
} 

工assは今噴射パラメータとしてISomeAbstractFactoryを受け入れる:

public class SomeClass 
{ 
    public SomeClass(ISomeAbstractFactory someAbstractfactory){...} 
} 

そして、それは大丈夫です。オブジェクトグラフを作成するコンポジションルートは1つだけです。 SomeClassにSomeAbstractFactoryを注入するようにUnityコンテナを設定します。

しかし、のは、クラスClassX1とClassX2は独自の依存関係を持っていると仮定しましょう:

public class ClassX1 : IClassX 
{ 
    public ClassX1(IClassA, IClassB) {...} 
} 

public class ClassX2 : IClassX 
{ 
    public ClassX2(IClassA, IClassC, IClassD) {...} 
} 

IClassA、IClassB、IClassCとIClassD依存関係を解決する方法は?

1. SomeAbstractFactoryコンストラクタを通して注入

私たちは、このようSomeAbstractFactoryするIClassA、IClassB、IClassCとIClassDの具体的な実装を注入することができます

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD) 
    {...} 
    ... 
} 

ユニティ・コンテナは、最初に使用されますコンストラクタルートを作成し、貧しい人のDIを使用して、パラメータrunTimeParamに基づいて具象ClassX1またはClassX2を返す。

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(classA, classB); 
      case 2: return new ClassX2(classA, classC, classD); 
       default : return new ClassDefault(); 
     } 
    } 
} 
このアプローチの

問題:

  • SomeAbstractFactoryは実際にそれに属し `tの依存関係について知っています。
  • オブジェクトグラフは、DIコンテナは、依存関係を解決するために使用されないであろうSomeAbstractFactoryコンストラクタとクラスの実装
  • 両方を変更するために必要となる、貧しいman`s DIは、DIに

2.明示的なコールを使用しなければならない深いですコンテナ

「new up up」ClassX1またはClassX2の代わりに、DIコンテナを使用して解決します。このアプローチ

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IUnityContainer container){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return container.Resolve<IClassX>("x1"); 
      case 2: return container.Resolve<IClassX>("x2"); 
       default : return container.Resolve<IClassX>("xdefault"); 
     } 
    } 
} 

問題:

  • DIコンテナがDIの解決法のみ合成ルート(のServiceLocatorアンチパターン)で使用されていないSomeAbstractFactory
  • に渡され

さらに適切なアプローチがありますか?

答えて

1

以下の例は、Unityでこれを行う方法を示しています。 This blog postはWindsorを使用して少し良く説明しています。根底にある概念は、まったく同じですが、わずかに異なる実装です。

私は抽象的な工場からコンテナにアクセスすることを許可します。抽象的なファクトリをコンテナへの依存を防ぐ手段として見る - 私のクラスはIFactoryにしか依存しないので、コンテナを使用するファクトリの実装のみです。 Castle Windsorは一歩進んで、工場のインターフェースを定義しますが、Windsorは実際の実装を提供します。しかし、両方のケースで同じアプローチが機能し、ファクトリインターフェイスを変更する必要はありません。

以下のアプローチでは、工場に依存するクラスが、どのインスタンスを作成するかを工場が決定するための引数を渡す必要があります。ファクトリはそれを文字列に変換し、コンテナはそれを名前付きインスタンスとマッチさせます。このアプローチは、UnityとWindsorの両方で機能します。

このようにすると、IFactoryに従うクラスは、ファクトリが正しい型を見つけるために文字列値を使用していることがわかりません。ウィンザーの例では、クラスはAddressオブジェクトをファクトリに渡し、ファクトリはそのオブジェクトを使用してアドレスの国に基づいてどのアドレスバリデータを使用するかを決定します。他のクラスはありませんが、工場は正しいタイプがどのように選択されているかを「知っています」。つまり、別のコンテナに切り替える場合は、の実装IFactoryの場合のみ変更する必要があります。 IFactoryに依存するものは変更しないでください。ここで

はユニティを使用して、サンプルコードです:

public interface IThingINeed 
{} 

public class ThingA : IThingINeed { } 
public class ThingB : IThingINeed { } 
public class ThingC : IThingINeed { } 

public interface IThingINeedFactory 
{ 
    IThingINeed Create(ThingTypes thingType); 
    void Release(IThingINeed created); 
} 

public class ThingINeedFactory : IThingINeedFactory 
{ 
    private readonly IUnityContainer _container; 

    public ThingINeedFactory(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public IThingINeed Create(ThingTypes thingType) 
    { 
     string dependencyName = "Thing" + thingType; 
     if(_container.IsRegistered<IThingINeed>(dependencyName)) 
     { 
      return _container.Resolve<IThingINeed>(dependencyName); 
     } 
     return _container.Resolve<IThingINeed>(); 
    } 

    public void Release(IThingINeed created) 
    { 
     _container.Teardown(created); 
    } 
} 

public class NeedsThing 
{ 
    private readonly IThingINeedFactory _factory; 

    public NeedsThing(IThingINeedFactory factory) 
    { 
     _factory = factory; 
    } 

    public string PerformSomeFunction(ThingTypes valueThatDeterminesTypeOfThing) 
    { 
     var thingINeed = _factory.Create(valueThatDeterminesTypeOfThing); 
     try 
     { 
      //This is just for demonstration purposes. The method 
      //returns the name of the type created by the factory 
      //so you can tell that the factory worked.     
      return thingINeed.GetType().Name; 
     } 
     finally 
     { 
      _factory.Release(thingINeed); 
     } 
    } 
} 

public enum ThingTypes 
{ 
    A, B, C, D 
} 

public class ContainerConfiguration 
{ 
    public void Configure(IUnityContainer container) 
    { 
     container.RegisterType<IThingINeedFactory,ThingINeedFactory>(new InjectionConstructor(container)); 
     container.RegisterType<IThingINeed, ThingA>("ThingA"); 
     container.RegisterType<IThingINeed, ThingB>("ThingB"); 
     container.RegisterType<IThingINeed, ThingC>("ThingC"); 
     container.RegisterType<IThingINeed, ThingC>(); 
    } 
} 

ここではいくつかのユニットテストです。彼らは、Create()関数に渡されたものを検査した後、工場が正しいタイプのIThingINeedを返すことを示しています。

この場合(該当する場合としない場合があります)、デフォルトとして1つのタイプも指定しました。要件に正確に一致するコンテナに何も登録されていない場合、そのデフォルトを返すことができます。そのデフォルトは、動作のないnullインスタンスでもあります。しかし、その選択はすべて工場とコンテナの設定にあります。

[TestClass] 
public class UnitTest1 
{ 
    private IUnityContainer _container; 

    [TestInitialize] 
    public void InitializeTest() 
    { 
     _container = new UnityContainer(); 
     var configurer = new ContainerConfiguration(); 
     configurer.Configure(_container); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     _container.Dispose(); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesExpectedType() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.B); 
     Assert.AreEqual(output, typeof(ThingB).Name); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesDefaultyTpe() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.D); 
     Assert.AreEqual(output, typeof(ThingC).Name); 
    } 
} 

この同じ工場はWindsorを使用して実装することができ、Windsorの例の工場はUnityで実行できます。

関連する問題