5

Sono confuso sull'implementazione di Dependency Injection in un esempio concreto.Utilizzo di Abstract Factory che viene iniettato tramite il contenitore DI

Diciamo che abbiamo una classe SomeClass che ha una dipendenza di tipo IClassX.

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

Creazione di implementazioni concrete di interfaccia IClassX dipende dal parametro di runtime N.

Con dato costruttore, non riesco a configurare il contenitore DI (l'Unità è utilizzato), perché io non so cosa attuazione IClassX verrà utilizzato in runtime. Mark Seemann nel suo libro Dependency Injection In .Net suggerisce che dovremmo usare Abstract Factory come parametro di iniezione.

Ora abbiamo SomeAbstractFactory che restituisce le implementazioni di IClassX in base al parametro runtime runTimeParam.

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 ora accetta ISomeAbstractFactory come parametro di iniezione:

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

e va bene. Abbiamo una sola radice di composizione in cui creiamo il grafico dell'oggetto. Configuriamo il contenitore Unity per iniettare SomeAbstractFactory su SomeClass.

Ma, supponiamo che le classi ClassX1 e ClassX2 hanno le loro dipendenze:

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

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

come risolvere IClassA, IClassB, IClassC e IClassD dipendenze?

1. iniezione attraverso il costruttore SomeAbstractFactory

Ci può iniettare implementazioni concrete di IClassA, IClassB, IClassC e IClassD a SomeAbstractFactory in questo modo:

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

contenitore Unità sarebbe stato utilizzato nella fase iniziale composizione radice e quindi utilizzare DI DI uomo povero per restituire calcestruzzo ClassX1 o ClassX2 in base al parametro 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(); 
     } 
    } 
} 

problemi con questo approccio:

  • SomeAbstractFactory sa di dipendenze che don `t realmente ne fanno parte.
  • Deeper grafico oggetto richiederebbe di modificare sia costruttore e classe di implementazione SomeAbstractFactory
  • DI contenitore non sarebbe usato per risolvere le dipendenze, poveri man`s DI deve essere utilizzato

2. chiamata esplicita a DI container

Invece di "newing up" ClassX1 o ClassX2, li risolveremo utilizzando un contenitore 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"); 
     } 
    } 
} 

problemi con questo approccio:

  • contenitore DI viene passato in SomeAbstractFactory
  • DI metodo Resolve non è usata solo alla radice composizione (ServiceLocator anti-modello)

C'è un altro approccio più adatto?

risposta

1

L'esempio seguente mostra come farlo con Unity. This blog post spiega un po 'meglio usando Windsor. Il concetto di base è esattamente lo stesso per ciascuna implementazione appena diversa.

Preferirei che la mia fabbrica astratta accedesse al container. Considero la fabbrica astratta come un modo per prevenire la dipendenza dal contenitore: la mia classe dipende solo da IFactory, quindi è solo l'implementazione della fabbrica che utilizza il contenitore. Il castello di Windsor fa un passo avanti: tu definisci l'interfaccia per la fabbrica ma Windsor fornisce l'effettiva implementazione. Ma è un buon segno che lo stesso approccio funziona in entrambi i casi e non è necessario modificare l'interfaccia di fabbrica.

Nell'approccio seguente, è necessario che la classe a seconda della fabbrica passi alcuni argomenti che consentono alla fabbrica di determinare quale istanza creare. Il factory lo convertirà in una stringa e il contenitore lo abbinerà a un'istanza denominata. Questo approccio funziona sia con Unity sia con Windsor.

In questo modo la classe che dipende da IFactory non sa che la fabbrica sta utilizzando un valore stringa per trovare il tipo corretto. Nell'esempio di Windsor una classe passa un oggetto Address alla fabbrica e la fabbrica utilizza quell'oggetto per determinare quale validatore di indirizzi utilizzare in base al paese dell'indirizzo. Nessun'altra classe, ma la fabbrica "sa" come viene selezionato il tipo corretto. Ciò significa che se si passa a un contenitore diverso, l'unica cosa che si deve cambiare è l'implementazione di IFactory. Nulla di ciò che dipende da IFactory deve cambiare.

Ecco il codice di esempio con Unity:

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

Ecco alcuni test di unità. Mostrano che la fabbrica restituisce il tipo corretto di IThingINeed dopo aver verificato ciò che è stato passato alla sua funzione Create().

In questo caso (che può essere o non essere applicabile) Ho anche specificato un tipo come predefinito. Se non viene registrato nulla con il contenitore che corrisponde esattamente al requisito, potrebbe restituire tale valore predefinito. Quella predefinita potrebbe anche essere un'istanza nulla senza comportamento. Ma tutta questa selezione è in fabbrica e nella configurazione del contenitore.

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

Questa stessa fabbrica può essere implementato utilizzando Windsor, e la fabbrica nell'esempio Windsor si potrebbe fare in Unity.