12

Questo è uno scenario di pattern decoratore abbastanza semplice, con la complicazione che il tipo decorato ha un parametro costruttore che dipende dal tipo in cui viene iniettato.Configurazione di Unity per risolvere un tipo che accetta una dipendenza decorata con un parametro che varia con il tipo in cui viene iniettata

devo un'interfaccia simile a questo:

interface IThing 
{ 
    void Do(); 
} 

E un'implementazione del genere:

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

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

e un decoratore come questo:

class DecoratingThing : IThing 
{ 
    IThing _innerThing; 

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

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

Infine, ho alcuni tipi che richiedono un IThing, chiamato Depender1, Depender2 ecc.

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

Voglio configurare un contenitore CIO per risolvere i casi di DependerX tale che essi vengono iniettati con RealThing decorato con un DecoratingThing. Importante: Ogni DependerXtipo richiede un diverso valore di configuration da passare al costruttore del suo RealThing, ad esempio "ConfigX" in ogni caso. per esempio. Il lavoro svolto dal contenitore CIO potrebbe essere:

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

... e così via.

In Unity, questo sembra abbastanza goffo configurare come devo mescolare il decoratore con la decorato:

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

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

E ripetere, violando DRY bene, per ogni DependerX.

Quello che mi piacerebbe fare è rimuovere la necessità di incorporare la costruzione del RealThing nella costruzione di DecoratingThing in ogni registrazione denominata di IThing - e dichiarare la decorazione solo una volta. È così, per esempio, che se la decorazione deve cambiare in futuro, è più facile riconfigurare. Il migliore mi è venuto in mente è questo metodo di supporto per la registrazione:

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

Ciò elimina la ripetizione almeno, ma ho ancora incorporare la costruzione del RealThing all'interno del DecoratingThing - questo significa che non posso variare la loro vita indipendentemente per esempio. Non riesco a registrare di nuovo IThing perché ho esaurito la mia registrazione di quell'interfaccia per il nome. Se voglio fare che devo introdurre un altro insieme di istanze denominate in questo modo:

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

È questo davvero l'opzione migliore? Sembra complesso e potenzialmente difficile per quelli che verranno dopo averli divorati. Gli altri contenitori IoC hanno un modo convincente per coprire questo scenario? Poiché il pattern per il funzionamento dell'iniezione viene ripetuto per ciascun DependerX, esiste un modo per utilizzare solo un'istanza denominata in alto (DependerX)?

Altri commenti?

risposta

5

Il design di classe in sé sembra ragionevole. Ecco un di configurazione basata su container convenzione che fa fondamentalmente questo:

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

Questa configurazione verrà automaticamente aggiungere tutte le classi che corrispondono al predicato sopra, quindi una volta che hai impostato in su, si può semplicemente aggiungere più classi (come Depender4 o Depender5) senza rivedere la configurazione del contenitore.

La configurazione sopra soddisfa queste unit test:

[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

Grazie Marco. Penso che un approccio basato sulla convenzione sia la strada da percorrere nel mio particolare scenario. Ciò non significa che altri approcci (come l'idea di intercettazione di cui sopra) non siano ugualmente validi. È bello sapere che non sono molto lontano da qui! –

3

Avete mai pensato di basare i vostri decoratori sulla funzionalità Unity Intercottion? Quindi sarebbe davvero facile dire "intercettare le chiamate a IThing usando questo Interceptor" una sola volta.

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

e allora sarebbe "iniettare questa IThing in questo e che Depender"

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

Considero questo come un approccio diverso alla decorazione piuttosto che un implemenation di esso. Ci ho pensato e penso che in questo caso particolare non mi sto occupando di una adeguata preoccupazione trasversale. È abbastanza specifico per quanto riguarda l'ambito e il comportamento del decoratore dipende dal metodo che viene chiamato. Penso che in questo caso probabilmente sposterei la complessità nella logica dell'intercettazione. Il decoratore che ho è preesistente, piuttosto complesso e usato altrove, quindi il refactoring non è banale. Date le informazioni nella domanda però, è un ottimo suggerimento quindi lo sto dando +1. –

Problemi correlati