12

Este es un escenario de patrón de decorador bastante sencillo, con la complicación de que el tipo decorado tiene un parámetro constructor que depende del tipo en que se está inyectando.Configurar Unity para resolver un tipo que toma una dependencia decorada que tiene un parámetro que varía con el tipo en el que se inyecta

que tienen una interfaz de esta manera:

interface IThing 
{ 
    void Do(); 
} 

Y una implementación de esta manera:

class RealThing : IThing 
{ 
    public RealThing(string configuration) 
    { 
     ... implementation ... 
    } 

    public void Do() 
    { 
     ... implementation ... 
    } 
} 

Y un decorador de la siguiente manera:

class DecoratingThing : IThing 
{ 
    IThing _innerThing; 

    public DecoratingThing(IThing thing) 
    { 
     _innerThing = thing;  
    } 

    public void Do() 
    { 
     _innerThing.Do(); 
    } 
} 

Por último, tengo algunos tipos que requieren un IThing, llamado Depender1, Depender2 etc.

class DependerX() 
{ 
    public DependerX(IThing thing) 
    { 
     ... implementation ... 
    } 
} 

Quiero configurar un contenedor COI para resolver los casos de DependerX de tal manera que se inyectan con RealThing decorado con una DecoratingThing. Importante: Cada DependerXtipo requiere un valor diferente de configuration que se pasa al constructor de su RealThing, diga "ConfigX" en cada caso. p.ej. El trabajo realizado por el contenedor IoC podría ser:

new Depender1(new DecoratingThing(new RealThing("Config1"))); 
new Depender2(new DecoratingThing(new RealThing("Config2"))); 

... y así sucesivamente.

en la unidad, esto parece bastante torpe para configurar ya que tengo que mezclar en el decorador con el decorado:

container.RegisterType<IThing, DecoratingThing>("ConfigX", 
    new InjectionFactory(container => new DecoratingThing(new RealThing("ConfigX")); 

container.RegisterType<DependerX>(
    new InjectionConstructor(new ResolvedParameter<IThing>("ConfigX"); 

y repetir, que viola SECO muy bien, para cada DependerX.

Lo que me gustaría hacer es eliminar la necesidad de integrar la construcción de RealThing en la construcción de DecoratingThing en cada registro con nombre de IThing - y declarar la decoración de una sola vez. Esto es así, por ejemplo, que si la decoración necesita cambiar en el futuro, es más fácil volver a configurarla. Lo mejor que se me ocurrió es este método de ayuda para la inscripción:

void RegisterDepender<TDepender>(IUnityContainer container, string config) 
{ 
    container.RegisterType<TDepender>(new InjectionConstructor(
     new ResolvedParameter<IThing>(config))); 
    container.RegisterType<IThing, DecoratingThing>(config, 
     new InjectionFactory(c => new DecoratingThing(new RealThing(config)))); 
} 

Esto elimina la repetición, al menos, pero todavía tienen que integrar la construcción de la RealThing dentro del DecoratingThing - esto significa que no puedo variar su vida independientemente por ejemplo. No puedo registrar IThing nuevamente para hacer esto porque he usado mi registro de esa interfaz para el nombre. Si yo quiero hacer eso tengo que introducir otro conjunto de instancias con nombre, así:

void RegisterDepender<TDepender>(IUnityContainer container, string config) 
{ 
    string realConfig = "Real" + config; 

    container.RegisterType<TDepender>(new InjectionConstructor(
     new ResolvedParameter<IThing>(config))); 
    container.RegisterType<IThing, DecoratingThing>(config, 
     new InjectionFactory(c => new DecoratingThing(
      container.Resolve<IThing>(realConfig)))); 
    container.RegisterType<IThing, RealThing>(realConfig, 
     new ContainerControlledLifetimeManager(), 
     new InjectionConstructor(config)); 
} 

¿Es esta realmente la mejor opción? Se siente complejo y potencialmente difícil para los que vendrán después de grok. ¿Tienen otros contenedores IoC una forma convincente de cubrir este escenario? Dado que el patrón de cómo funciona la inyección se repite para cada DependerX, ¿hay alguna forma de usar solo una instancia con nombre en el nivel superior (DependerX)?

¿Algún otro comentario?

Respuesta

5

El propio diseño de la clase parece razonable. He aquí una configuración del contenedor basado en la convención que hace básicamente esto:

public class MyConventions : UnityContainerExtension 
{ 
    protected override void Initialize() 
    { 
     var dependers = from t in typeof(IThing).Assembly.GetExportedTypes() 
         where t.Name.StartsWith("Depender") 
         select t; 

     foreach (var t in dependers) 
     { 
      var number = t.Name.TrimStart("Depender".ToArray()); 
      var realName = "Real" + number; 
      var decoName = "Deco" + number; 
      var config = "Config" + number; 
      this.Container.RegisterType<IThing, RealThing>(realName, 
       new InjectionConstructor(config)); 
      this.Container.RegisterType<IThing, DecoratingThing>(decoName, 
       new InjectionConstructor(
        new ResolvedParameter<IThing>(realName))); 
      this.Container.RegisterType(t, 
       new InjectionConstructor(
        new ResolvedParameter<IThing>(decoName))); 
     } 
    } 
} 

Esta configuración se añadirá automáticamente a todas las clases que coinciden con el predicado anterior, por lo que una vez que haya configurado, sólo se puede añadir más clases (como Depender4 o Depender5) sin volver a visitar la configuración del contenedor.

La configuración anterior satisface estas pruebas de unidad:

[Fact] 
public void ContainerCorrectlyResolvesDepender1() 
{ 
    var container = new UnityContainer().AddNewExtension<MyConventions>(); 
    var actual = container.Resolve<Depender1>(); 

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing); 
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing); 
    Assert.Equal("Config1", thing.Configuration); 
} 

[Fact] 
public void ContainerCorrectlyResolvesDepender2() 
{ 
    var container = new UnityContainer().AddNewExtension<MyConventions>(); 
    var actual = container.Resolve<Depender2>(); 

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing); 
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing); 
    Assert.Equal("Config2", thing.Configuration); 
} 

[Fact] 
public void ContainerCorrectlyResolvesDepender3() 
{ 
    var container = new UnityContainer().AddNewExtension<MyConventions>(); 
    var actual = container.Resolve<Depender3>(); 

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing); 
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing); 
    Assert.Equal("Config3", thing.Configuration); 
} 
+0

Gracias Mark. Creo que un enfoque basado en convenciones es el camino a seguir en mi escenario particular. Eso no quiere decir que otros enfoques (como la idea de interceptación anterior) no sean igualmente válidos. ¡Es bueno saber que no estoy fuera de los caminos trillados aquí! –

3

¿Alguna vez ha pensado en basar sus decoradores en la funcionalidad de intercepción de Unity? Entonces sería muy fácil decir "interceptar las llamadas al IThing usando este Interceptor" solo una vez.

container.AddNewExtension<Interception>(); 
container.RegisterType<IThing>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<DecoratingThingBehavior>()); 

y entonces sería "inyecte esta IThing en esto y que Depender"

container.RegisterType<Depender1>(new InjectionConstructor(new ResolvedParameter<IThing>("myNameForThing"))); 
+0

Veo esto como un enfoque diferente para la decoración en lugar de un implemenation de ella. Lo he pensado y creo que en este caso particular no estoy tratando con una preocupación transversal adecuada. Es bastante específico en alcance y el comportamiento del decorador depende del método que se llame. Creo que en este caso probablemente cambiaría la complejidad a la lógica de la intercepción. El decorador que tengo es preexistente, bastante complejo y se usa en otro lado, por lo que la refactorización no es trivial. Sin embargo, dada la información en la pregunta, es una gran sugerencia, así que le doy +1. –

Cuestiones relacionadas