6

Ho sperimentato l'utilizzo di delegati denominati anziché interfacce a metodo singolo. Questo ha alcuni vantaggi per le dimensioni del codice, come si può andare da (qualche interruzione di linea asportate per non sovrastimare il caso):C'è un modo semplice per registrare chiusure statiche con Castle Windsor?

public interface IProductSource 
{ 
    IEnumerable<Product> GetProducts(); 
} 
public class DataContextProductSource : IProductSource 
{ 
    private readonly DataContext _DataContext; 
    public DataContextProductSource(DataContext dataContext) 
    { 
     if (dataContext == null) throw new ArgumentNullException("dataContext"); 
     _DataContext = dataContext; 
    } 
    public IEnumerable<Product> GetProducts() 
    { 
     return _DataContext.Products.AsEnumerable(); 
    } 
} 

a:

public delegate IEnumerable<Product> DGetProducts(); 
public static class DataContextFunctions 
{ 
    public DGetProducts GetProducts(DataContext dataContext) 
    { 
     if (dataContext == null) throw new ArgumentNullException("dataContext"); 
     return() => dataContext.Products.AsEnumerable(); 
    } 
} 

questo è fondamentalmente approfittando del fatto che quando si va abbastanza avanti con l'iniezione di dipendenza, molte classi diventano poco più che chiusure. Queste classi possono essere sostituite con funzioni che restituiscono lambda. Intere serie di funzioni correlate (che non hanno bisogno di incapsulare uno stato mutabile, ma sarebbero state espresse usando le classi nell'iniezione di dipendenza "standard"), possono quindi essere raggruppate in una classe statica (o "modulo" in VB parlance) .

Questo è tutto a posto, ma ho difficoltà a trovare il modo migliore per registrare questi metodi statici con Castle Windsor. Metodi senza dipendenze sono facili:

Component.For<DGetIntegers>().Instance(Integers.GetOneToTen) 

Ma i nostri DataContextFunctions.GetProducts dall'alto ha alcune dipendenze. Il modo migliore che ho trovato per registrare questo è:

Component.For<DGetProducts>().UsingFactoryMethod(
    kernel => DataContextFunctions.GetProducts(kernel.Resolve<DataContext>()) 

Questo può diventare piuttosto prolisso, e ovviamente dover chiedere il kernel direttamente per ogni tipo di dipendenza sconfigge il punto un po '. Mi sembra che siano disponibili tutte le informazioni statiche di cui un contenitore ha bisogno, quindi dovrebbe essere possibile farlo.

La domanda è: Castle Windsor (o qualsiasi altro contenitore) ha un modo semplice per fare ciò che ho perso, o ci sono problemi tecnici che sorgono, o è solo troppo di nicchia un caso d'uso per essere stato incluso?

risposta

4

Questo è un approccio interessante. Penso che potresti farlo funzionare abbastanza facilmente con un attivatore personalizzato.

+0

Grazie, Krzysztof. Ci proverò qualche volta e riferirò sui risultati. Qualche link per un buon punto di partenza? – ninjeff

+0

@ninjeff se sei su 2.5.x la documentazione dovrebbe farti iniziare docs.castleproject.org/(S(hucszcu5ilznbv45fvrim355))/... Se sei su 3.0 beta, ci sono state alcune modifiche API anche se i concetti sono ancora stesso. Fammi sapere se il documento non è perfetto –

+0

Sono riuscito a farlo funzionare. Pubblicherò un link e una descrizione con la mia risposta in un'ora (gli utenti low-karma come me non possono rispondere alle nostre domande entro otto ore). – ninjeff

10

Risposta breve

Stai cercando di fare un DI Container (Windsor Castle) svolgere la funzione composizione, ma in realtà destinati a oggetto composizione. Questo ti darà un sacco di attriti. La mia ipotesi è che otterrai la stessa esperienza con altri contenitori.

DI I contenitori sono progettati attorno a concetti orientati agli oggetti, in particolare SOLID. Funzionano molto bene con questi principi perché sono stati progettati con la comprensione intrinseca di cose come Iniezione del Costruttore e Cablaggio automatico.

Non c'è niente di sbagliato in un approccio più funzionale, ma devo ancora vedere un contenitore DI costruito attorno alla composizione delle funzioni anziché alla composizione dell'oggetto.

Risposta lunga

L'uso dei delegati come principio generale per il DI tende ad essere problematico in lingue staticamente tipizzati (almeno in NET) per un paio di motivi. Concettualmente non c'è nulla di sbagliato in questo approccio poiché uno delegate can be considered as an anonymous Role Interface. Tuttavia, tende a diventare ingombrante a causa dell'ambiguità di tipo.

L'approccio più comune che vedo normalmente è quello di utilizzare i delegati incorporati del BCL, come ad esempio Func<T>, Action<T> e così via.Tuttavia, potresti avere molti consumatori diversi che si affidano a Func<string> nel qual caso le cose diventano ambigue - solo perché un consumatore richiede uno Func<string> non significa che richieda il delegato nello stesso ruolo. Sebbene sia meccanicamente possibile utilizzare DI con delegati, ciò che accade è che i delegati nascondono i ruoli dell'applicazione. I ruoli scompaiono, lasciando solo la meccanica.

Si potrebbe quindi, come suggerito nel PO definire delegati personalizzati per ogni ruolo, come suggerito da questo delegato:

public delegate IEnumerable<Product> DGetProducts(); 

Tuttavia, se si prende questa strada, allora non si guadagna nulla. Un "delegato di ruolo" deve essere ancora definito per ciascun ruolo. Contrasto che a definire un'interfaccia simile e dovrebbe essere chiaro che l'unico risparmio è un paio di parentesi angolari e una definizione di metodo esplicito:

public interface IProductSource { IEnumerable<Product> GetProducts(); } 

Questo non è un sacco di spese generali (se presente).

Si consiglia inoltre di dare un'occhiata a questo dibattito: http://thorstenlorenz.wordpress.com/2011/07/23/dependency-injection-is-dead-long-live-verbs/

+0

Grazie per la risposta, Mark. In realtà non sto cercando di passare ad un approccio completamente funzionale, ma sperimentando un approccio ibrido. Cercando di sentire i lati positivi e negativi dell'uso di funzioni vs oggetti per vari concetti e di vedere quanto essi interagiscono. – ninjeff

+0

Per quanto riguarda il sovraccarico delle interfacce, è più nella lunghezza delle classi di implementazione piuttosto che nelle interfacce stesse. – ninjeff

1

Sulla base dei puntatori di Krzysztof, sono riuscito a scrivere un attivatore personalizzato per gestirlo. La fonte è github.

Registra delegati statiche come questo (seguendo l'esempio in questione):

container.Register(Component. 
    For<DGetProducts>(). 
    ImplementedBy(typeof(DataContextFunctions)). 
    Named("GetProducts"). 
    Activator<StaticDelegateActivator>()); 

Il codice è in gran parte una riscrittura di DefaultComponentActivator. Avevo anche bisogno di copiare e incollare DependencyTrackingScope dal codice sorgente di Windsor, poiché è interno.

Il singolo "test" nel progetto Tests copre solo un caso singolo. Non sospetto che funzionerà per scenari più avanzati come i proxy.

Ho accettato la risposta di Krzysztof, poiché la sua guida ha portato alla soluzione.

Problemi correlati