2012-06-06 15 views
10

Ho una catena di dipendenza che appare più o meno in questo modo:Ninject Bind Quando antenato del tipo 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 { ... } 

Fino ad ora, ho usato le convenzioni attacchi basati, ma che non è abbastanza buono più perché ho più di una implementazione per IFtpSettings. Quindi ho deciso di utilizzare alcuni binding contestuali. A prima vista kernel.Bind<>().To<>().WhenInjectedInto<>() sembrava promettente, ma che aiuta solo al primo livello, il che significa che se avessi una CarSalesFtpFileProvider e MotorcycleSalesFtpProvider ho potuto fare questo:

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

ma sembra abbastanza stupido per creare due implementazioni concrete di FtpFileProvider in realtà differiscono solo su quali impostazioni voglio che usino. Ho visto che esiste un metodo chiamato WhenAnyAnchestorNamed(string name). Ma questa strada mi impone di inserire attributi e stringhe magiche nei miei lavori batch, di cui non sono entusiasta.

Ho anche notato che v'è una pianura .When(Func<IRequest, bool>) vecchio metodo sulle dichiarazioni vincolanti, così mi si avvicinò con questo come le mie dichiarazioni rilegatura:

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

Quindi, se un costruttore chiede IFtpSettings, abbiamo recurse su richiesta albero per vedere se qualcuno dei servizi/tipi richiesti nella catena corrisponde al tipo fornito (CarSalesBatchJob o MotorcycleSalesBatchJob) e in tal caso restituisce true. Se arriviamo fino in cima alla catena, restituiamo false.

Ci scusiamo per la lunga spiegazione dello sfondo.

Ecco la mia domanda: c'è qualche ragione per cui non dovrei affrontare il problema in questo modo? È considerata una cattiva forma? C'è un modo migliore per trovare i tipi di richiesta degli antenati? Dovrei ristrutturare la mia classe/catena di dipendenza in modo più "gradevole"?

+2

Credo che la soluzione che hai identificato nella tua domanda, per implementare due classi concrete che accoppiano un FtpFileProvider con un'implementazione di IFtpSettings, è la più facile da capire/leggere/debug/hand off. Altre soluzioni richiedono allo sviluppatore di saperne di più sul tuo strumento IOC specifico e su come lo hai configurato. Questa implementazione segue anche Convention over Configuration per Ninject. –

risposta

6

È necessario utilizzare request.Target.Member.ReflectedType anziché request.Service Questo è il tipo di implementazione.

Anche WhenAnyAncestorNamed non richiede Attributi. Puoi contrassegnare i binding dei tuoi lavori usando il metodo Named.

4

Questo non è davvero una risposta alla tua domanda, ma è possibile risolvere il problema scrivendo una singola classe come segue:

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

In questo caso, la configurazione sarà simile a questa:

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

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

Si prega di notare che nella mia esperienza, ho scoperto che nella maggior parte dei casi in cui ritenete di aver bisogno di un'iniezione basata sul contesto, in realtà avete un difetto nel vostro progetto. Tuttavia, con le informazioni fornite, è impossibile per me dire nulla al riguardo nel tuo caso, ma potresti voler dare un'occhiata al tuo codice. Potresti essere in grado di ridefinire il tuo codice in modo tale da non aver bisogno di un'iniezione basata sul contesto.

1

Il mio consiglio è di indirizzare la destinazione della situazione di rottura della convention con la registrazione, non con IFtpSettings.Per esempio, in una situazione simile farei quanto segue:

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

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

Questo è quanto di straight-forward come si può ottenere quando si tratta di spiegare ad altri programmatori come viene creata un'istanza di ogni lotto di lavoro. Invece di prendere di mira la registrazione di ICommonSetting per cercare di gestire ogni singola azione, gestisci ciascuna una tantum nel suo caso.

Per dirla in un altro modo, immagina se queste classi avessero due dipendenze che dovevano essere modificate nel contenitore IoC. Avresti quattro righe di codice di registrazione in luoghi diversi, ma avrebbero tutte lo scopo di creare un'istanza di MotorcycleSalesBatchJob, o CarSalesBatchJob, ecc. Se qualcuno volesse scoprire come la classe fosse referenziata, avrebbe dovuto cercare qualsiasi riferimento a una classe (o una classe base). Perché non scrivere semplicemente un codice che spiega esattamente come ciascuno di questi deve essere istanziato, tutto in un posto?

Lo svantaggio di questo (o almeno quello che ho sentito da altri) è che se il costruttore di una di queste classi concrete cambia, allora il codice si interromperà e dovrai modificare la registrazione. Bene, per me, questo è positivo, perché ho già fatto un passo in giù in questo tipo di percorso con il contenitore IoC che cambia in base a una sorta di stato, I ho bisogno di per assicurarmi che mantenga ancora il comportamento previsto.

Diventa ancora più divertente quando si pensa alle possibilità. Si potrebbe fare qualcosa di simile:

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

tutta la complessità di come le cose potrebbero funzionare in situazioni diverse possono essere suddivisi in classi separate, lasciando al contenitore CIO di fornire la classe corretta nella giusta situazione.

Problemi correlati