5

Ich bin verwirrt über Dependency Injection-Implementierung in einem konkreten Beispiel.Arbeiten mit Abstract Factory, die durch DI-Container injiziert wird

Nehmen wir an, wir haben eine SomeClass-Klasse, die eine Abhängigkeit vom Typ IClassX hat.

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

Erstellung von konkreten Implementierungen von IClassX Schnittstelle ist abhängig von Laufzeitparameter N.

Mit gegeben Konstruktor, kann ich nicht DI-Container konfigurieren (Unity verwendet wird), weil ich nicht weiß, was die Umsetzung von IClassX wird zur Laufzeit verwendet. Mark Seemann in seinem Buch Dependency Injection In. Net schlägt vor, dass wir Abstract Factory als Injektionsparameter verwenden sollten.

Jetzt haben wir SomeAbstractFactory, die Implementierungen von IClassX basierend auf dem Runtime-Parameter runTimeParam zurückgibt.

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(); 
     } 
    } 
} 

Someclass akzeptiert nun ISomeAbstractFactory als Injektionsparameter:

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

Und das ist in Ordnung. Wir haben nur eine Kompositionswurzel, in der wir das Objektdiagramm erstellen. Wir konfigurieren den Unity-Container so, dass SomeAbstractFactory in SomeClass eingefügt wird.

Aber nehmen wir an, dass Klassen ClassX1 und ClassX2 ihre eigenen Abhängigkeiten:

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

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

Wie IClassA, IClassB, IClassC und IClassD Abhängigkeiten lösen?

1. Injektion durch SomeAbstractFactory Konstruktor

können wir konkrete Implementierungen von IClassA, IClassB, IClassC und IClassD injizieren, so SomeAbstractFactory:

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

Unity Container würde in der Anfangs verwendet werden Zusammensetzung Wurzel und dann verwenden Sie DI des armen Mannes, um konkrete ClassX1 oder ClassX2 basierend auf Parameter runTimeParam

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(); 
     } 
    } 
} 
zurückzugeben

Probleme mit diesem Ansatz:

  • SomeAbstractFactory weiß um Abhängigkeiten, die wirklich zu ihr gehören `t.
  • Deeper Objektgraphen würde erfordern, sowohl SomeAbstractFactory Konstruktor und Klassenimplementierung würde nicht verwendet werden
  • DI-Container ändern Abhängigkeiten zu lösen, muß armen Mann `s DI

2. expliziten Aufruf DI verwendet werden Container

Anstatt ClassX1 oder ClassX2 neu zu erstellen, würden wir sie mithilfe eines DI-Containers beheben.

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"); 
     } 
    } 
} 

Probleme mit diesem Ansatz:

  • DI Behälter in SomeAbstractFactory geben wird
  • DI Resolve Verfahren nicht nur an der Zusammensetzung Wurzel (Servicelocator anti-Muster)

verwendet wird Gibt es einen anderen geeigneten Ansatz?

Antwort

1

Das folgende Beispiel zeigt, wie Sie dies mit Unity tun können. This blog post erklärt es ein wenig besser mit Windsor. Das zugrundeliegende Konzept ist für jede genau dieselbe, nur etwas andere Implementierung.

Ich würde lieber meine abstrakte Fabrik auf den Container zugreifen lassen. Ich sehe die abstrakte Fabrik als eine Möglichkeit, die Abhängigkeit vom Container zu verhindern - meine Klasse hängt nur von IFactory ab, also ist es nur die Implementierung der Fabrik, die den Container verwendet. Castle Windsor geht noch einen Schritt weiter - Sie definieren die Schnittstelle für die Fabrik, aber Windsor bietet die eigentliche Implementierung. Aber es ist ein gutes Zeichen, dass der gleiche Ansatz in beiden Fällen funktioniert und Sie die Factory-Schnittstelle nicht ändern müssen.

In der folgenden Vorgehensweise ist es erforderlich, dass die Factory-abhängige Klasse ein Argument übergibt, mit dem die Factory bestimmen kann, welche Instanz erstellt werden soll. Die Factory wird dies in eine Zeichenfolge konvertieren, und der Container wird sie mit einer benannten Instanz abgleichen. Dieser Ansatz funktioniert sowohl mit Unity als auch mit Windsor.

Auf diese Weise weiß die Klasse in Abhängigkeit von IFactory nicht, dass die Factory einen Zeichenfolgenwert verwendet, um den richtigen Typ zu finden. Im Windsor-Beispiel übergibt eine Klasse ein Objekt Address an das Werk, und das Werk verwendet dieses Objekt, um zu bestimmen, welcher Adressvalidierer basierend auf dem Land der Adresse verwendet werden soll. Keine andere Klasse als die Fabrik "weiß", wie der richtige Typ ausgewählt wird. Das heißt, wenn Sie zu einem anderen Container wechseln, müssen Sie nur die Implementierung von IFactory ändern. Nichts, was von IFactory abhängt, muss sich ändern.

Hier Codebeispiel Unity mit:

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>(); 
    } 
} 

Hier einige Unit-Tests. Sie zeigen, dass die Fabrik den richtigen Typ von IThingINeed zurückgibt, nachdem sie überprüft hat, was an ihre Create()-Funktion übergeben wurde.

In diesem Fall (was möglicherweise nicht zutreffend sein kann) habe ich auch einen Typ als Standard angegeben. Wenn für den Container, der genau der Anforderung entspricht, nichts registriert ist, kann dieser Standardwert zurückgegeben werden. Dieser Standardwert könnte auch eine Null-Instanz ohne Verhalten sein. Aber diese Auswahl ist in der Fabrik- und Behälterkonfiguration.

Dieselbe Fabrik kann mit Windsor implementiert werden, und die Fabrik im Windsor-Beispiel könnte in Unity erfolgen.

Verwandte Themen