2012-06-06 13 views
10

Mam łańcuch zależności, który wygląda mniej więcej tak:Ninject Bind Kiedy przodek typ T

public class CarSalesBatchJob 
{ 
    public CarSalesBatchJob(IFileProvider fileProvider) 
    { ... } 
} 

public class MotorcycleSalesBatchJob 
{ 
    public MotorcycleSalesBatchJob(IFileProvider fileProvider) 
    { ... } 
}  

public class FtpFileProvider : IFileProvider 
{ 
    public FtpFileProvider(IFtpSettings settings) 
    { ... } 
} 

public class CarSalesFtpSettings : IFtpSettings { ... } 
public class MotorcycleSalesFtpSettings : IFtpSettings { ... } 

Do tej pory używam konwencje oparte wiązania, ale to nie jest wystarczająco dobre więcej, ponieważ mam więcej niż jedną implementację dla IFtpSettings. Zdecydowałem się więc użyć niektórych kontekstowych wiązań. Na pierwszy rzut oka kernel.Bind<>().To<>().WhenInjectedInto<>() wyglądał obiecująco, ale to tylko pomaga na pierwszym poziomie, co oznacza, że ​​jeśli miałem CarSalesFtpFileProvider i MotorcycleSalesFtpProvider mogę to zrobić:

kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>() 
    .WhenInjectedInto<CarSalesFtpFileProvider>(); 
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>() 
    .WhenInjectedInto<MotorcycleSalesFtpFileProvider>(); 

Ale wydaje się dość głupi, aby utworzyć dwie konkretne implementacje FtpFileProvider tak naprawdę różnią się tylko tym, jakich ustawień chcę użyć. Widziałem, że istnieje metoda o nazwie WhenAnyAnchestorNamed(string name). Ale ta trasa wymaga od mnie nakładania atrybutów i magicznych ciągów na moje zadania wsadowe, których nie jestem zachwycony.

Zauważyłem również, że nie jest to zwykły stary .When(Func<IRequest, bool>) metoda na wiążące deklaracje, więc wpadłem na to jak my wiążących stwierdzeń:

//at this point I've already ran the conventions based bindings code so I need to unbind 
kernel.Unbind<IFtpSettings>(); 
kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>() 
    .When(r => HasAncestorOfType<CarSalesBatchJob>(r)); 
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>() 
    .When(r => HasAncestorOfType<MotorcycleSalesBatchJob>(r)); 

// later on in the same class 
private static bool HasAncestorOfType<T>(IRequest request) 
{ 
    if (request == null) 
     return false; 

    if (request.Service == typeof(T)) 
     return true; 

    return HasAncestorOfType<T>(request.ParentRequest); 
} 

Więc jeśli konstruktor prosi o IFtpSettings, my recurse się żądanie drzewo, aby sprawdzić, czy którakolwiek z żądanych usług/typów w łańcuchu jest zgodna z podanym typem (CarSalesBatchJob lub MotorcycleSalesBatchJob), a jeśli tak, zwróci true. Jeśli dojdziemy do końca łańcucha, zwracamy fałsz.

Przepraszamy za długie objaśnienie tła.

Oto moje pytanie: czy istnieje jakikolwiek powód, dla którego nie powinienem podejść do problemu w ten sposób? Czy uważa się to za złą formę? Czy istnieje lepszy sposób na znalezienie typów żądań przodków? Czy powinienem zrestrukturyzować moje klasy/łańcuch zależności w bardziej "przyjemny" sposób?

+2

Uważam, że rozwiązanie, które zidentyfikowałeś w swoim pytaniu, aby zaimplementować dwie konkretne klasy, które łączą FtpFileProvider z implementacją IFtpSettings, jest najłatwiejsze do zrozumienia/odczytu/debugowania/przekazania. Inne rozwiązania wymagają od programisty wiedzy na temat Twojego konkretnego narzędzia IOC i sposobu jego konfiguracji. Ta implementacja jest również zgodna z Konwencją dotyczącą konfiguracji programu Ninject. –

Odpowiedz

6

Powinieneś użyć request.Target.Member.ReflectedType zamiast request.Service To jest typ implementacji.

Również WhenAnyAncestorNamed nie wymaga atrybutów. Możesz oznaczyć powiązania swoich prac przy użyciu metody Named.

4

Nie jest to odpowiedź na twoje pytanie, ale można rozwiązać problem pisząc jedną klasę następująco:

private sealed class FtpFileProvider<TFileProvider> 
    : FtpFileProvider 
    where TFileProvider : IFileProvider 
{ 
    public FtpFileProvider(TFileProvider settings) 
     : base(settings) { } 
} 

W takim przypadku konfiguracja może wyglądać następująco:

kernel.Bind<IFileProvider>() 
    .To<FtpFileProvider<CarSalesFtpSettings>>() 
    .WhenInjectedInto<CarSalesBatchJob>(); 

kernel.Bind<IFileProvider>() 
    .To<FtpFileProvider<MotorcycleSalesFtpSettings>>() 
    .WhenInjectedInto<MotorcycleSalesBatchJob>(); 

Zauważ, że z mojego doświadczenia wynika, że ​​w większości przypadków, gdy wydaje Ci się, że potrzebujesz zastrzyku z kontekstem, faktycznie masz wadę w swoim projekcie. Jednak przy podanych informacjach nie mogę w tej sprawie powiedzieć nic na ten temat, ale warto rzucić okiem na swój kod. Możliwe, że będziesz w stanie zreorganizować swój kod w taki sposób, że nie będziesz potrzebował zastrzyku z kontekstem.

1

Moja rada polega na wskazaniu miejsca docelowego sytuacji zerwania konwencji przy rejestracji, a nie w ustawieniach IFtpSettings.Na przykład, w podobnej sytuacji chciałbym wykonać następujące czynności:

container.Register<CarSalesBatchJob>(() => { 
    ICommonSetting myCarSpecificDependency = container.Resolve<CarSpecificDependency>(); 
    new CarSalesBatchJob(myCarSpecificDependency); 
}); 

container.Register<MotorcycleSalesBatchJob>(() => { 
    ICommonSetting myMotorcycleSpecificDependency = container.Resolve<MotorcycleSpecificDependency>(); 
    new MotorcycleSalesBatchJob(myMotorcycleSpecificDependency); 
}); 

To jest tak prosta, jak może być, jeśli chodzi o wyjaśnianie innych programistów, jak każda partia Pracy jest tworzony. Zamiast kierować rejestrację ICommonSetting, aby spróbować poradzić sobie z każdym z nich, traktujesz każdy przypadek jednorazowo we własnym przypadku.

Mówiąc inaczej, wyobraź sobie, że te klasy miały dwie zależności, które wymagały zmiany w kontenerze IoC. Będziesz miał cztery wiersze kodu rejestracyjnego w różnych miejscach, ale wszystko to w celu utworzenia egzemplarza MotorcycleSalesBatchJob, lub CarSalesBatchJob itp. Jeśli ktoś chciałby się dowiedzieć, w jaki sposób klasa została przywołana, musiałby polować na wszelkie odniesienia do klasy (lub klasy bazowej). Dlaczego po prostu nie napisać kodu, który dokładnie wyjaśnia, w jaki sposób każda z nich ma być utworzona, wszystko w jednym miejscu?

Wadą tego (a przynajmniej tego, co słyszałem od innych) jest to, że jeśli konstruktor jednej z tych konkretnych klas się zmieni, kod się zepsuje i będziesz musiał zmienić rejestrację. Cóż, dla mnie jest to pozytywne, ponieważ zrobiłem już krok w dół tego typu ścieżki, zmieniając kontener IoC na podstawie pewnego stanu, I potrzebuję, aby upewnić się, że nadal zachowuję oczekiwane zachowanie.

Jeszcze lepiej się bawić, gdy myśli się o możliwościach. Można zrobić coś takiego:

container.Register<IProductCatalog>(() => { 
    currentState = container.Resolve<ICurrentState>().GetTheState(); 
    if (currentState.HasSpecialPricing()) 
     return container.Resolve<SpecialPricingProductCatalog>(); 
    return container.Resolve<RegularPricingProductCatalog>(); 
}); 

Cała złożoność jak rzeczy mogą pracować w różnych sytuacjach można podzielić na odrębne klasy, pozostawiając je do kontenera IoC dostarczyć właściwą klasę we właściwej sytuacji.

Powiązane problemy