12

Jest to dość prosty scenariusz z wzorami dekoracyjnymi, z komplikacją, że dekorowany typ ma parametr konstruktora, który zależy od typu, w który jest wstrzykiwany.Konfigurowanie Unity w celu rozwiązania typu, który ma udekorowaną zależność, która ma parametr zmieniający się w zależności od typu, w który jest wstrzykiwany.

Mam interfejs tak:

interface IThing 
{ 
    void Do(); 
} 

i wdrożenie takiego:

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

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

oraz dekoratora takiego:

class DecoratingThing : IThing 
{ 
    IThing _innerThing; 

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

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

Wreszcie, mam kilka typów które wymagają IThing, o nazwie Depender1, Depender2 itd ..

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

Chcę skonfigurować kontenera IOC rozwiązywać przypadki DependerX taki sposób, że są one wstrzyknięto RealThing ozdobione DecoratingThing. Ważne: Każdy typ DependerX typu wymaga podania innej wartości configuration do konstruktora jej RealThing, powiedzmy "ConfigX" w każdym przypadku. na przykład Praca wykonana przez kontener IoC może być:

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

... i tak dalej.

w jedności, to wydaje się dość niezgrabne skonfigurować jako muszę mieszać w dekoratora z zdobione:

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

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

I powtarzam, naruszenie DRY przyjemnie, dla każdego DependerX.

Co chcę zrobić, to usunąć potrzebę wbudowania konstrukcji RealThing w konstrukcji DecoratingThing w każdej wymienionej rejestracji IThing - i zadeklarować dekorację tylko raz. Dzieje się tak, na przykład, jeśli dekoracja musi się zmienić w przyszłości, łatwiej jest zmienić konfigurację. Najlepszym wymyśliłem jest ta metoda pomocnika do rejestracji:

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

Usuwa powtórzenie co najmniej, ale wciąż muszę umieścić budowę RealThing wewnątrz DecoratingThing - oznacza to, że nie można zmieniać ich żywotność niezależnie na przykład. Nie mogę ponownie zarejestrować IThing, ponieważ wykorzystałem moją rejestrację tego interfejsu dla nazwy. Jeśli chcę, aby to zrobić muszę wprowadzić inny zestaw nazwanych wystąpień tak:

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

Czy to naprawdę najlepszym rozwiązaniem? Wydaje się to skomplikowane i potencjalnie trudne dla tych, którzy przyjdą po grzebaniu. Czy inne kontenery IoC mają nieodparty sposób na pokrycie tego scenariusza? Ponieważ wzorzec działania wtrysku jest powtarzany dla każdego DependerX, czy istnieje sposób użycia tylko nazwanej instancji na najwyższym poziomie (DependerX)?

Jakieś inne komentarze?

Odpowiedz

5

Sama konstrukcja klasa wydaje się rozsądne. Oto konfiguracja pojemnik konwencja oparte że w zasadzie robi to:

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

Konfiguracja ta automatycznie doda wszystkie klasy, które odpowiadają wyżej orzeczenie, więc kiedy już go skonfigurować, można po prostu dodać więcej klas (jak Depender4 lub Depender5) bez ponownego przeglądania konfiguracji kontenera.

Powyższy układ spełnia te testy jednostkowe:

[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

Dzięki Mark. Sądzę, że podejście oparte na konwencjach jest sposobem na zastosowanie w moim konkretnym scenariuszu. Nie oznacza to, że inne podejścia (takie jak powyższy pomysł przechwytywania) nie są jednakowe. Miło słyszeć, że nie jestem tu na uboczu! –

3

Czy kiedykolwiek zastanawiałeś się nad oparciem swoich dekoratorów na funkcji Unity Interception? Wtedy byłoby naprawdę łatwo powiedzieć "przechwyć połączenia do IThing używając tego Interceptora" tylko raz.

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

a potem będzie „wstrzyknąć ten IThing do tego i że Depender

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

ja to jako inne podejście do dekoracji raczej niż implementacja tego. Zastanowiłem się nad tym i myślę, że w tym konkretnym przypadku tak naprawdę nie mam do czynienia z problemem przekrojowym. Ma dość specyficzny zakres, a zachowanie dekoratora zależy od wywoływanej metody. Myślę, że w tym przypadku prawdopodobnie przesuwałbym złożoność w logikę przechwytywania. Posiadany przeze mnie dekorator jest już wcześniejszy, dość skomplikowany i używany gdzie indziej, więc refaktoryzacja nie jest banalna. Biorąc pod uwagę informacje zawarte w pytaniu, jest to świetna propozycja, więc daję +1. –

Powiązane problemy