Inversion of Control e Dependecy Injection fatti in casa (3)
Finalmente siamo giunti all'ultimo capitolo della saga iniziata con i precedenti due post riguardanti l'introduzione all'IoC e l'implementazione dei plugins; finalmente siamo giunti al nocciolo della questione; finalmente, senza indugio, passiamo a vedere qualche cosa di interessante...
Il pezzo che manca a completare il puzzle è capire come dare la possibilità alla nostra applicazione di generare un'istanza di un particolare oggetto, che implementa l'interfaccia "IFinder", utilizzando una semplice stringa di testo. La struttura che permette la "magia" è la classe statica "FinderIoc", e il suoo metodo "CreateProvider". Come visto in precedenza, l'utilizzo della stessa è estremamente banale; l'implementazione nasconde qualche piccola insidia, dovendo fare uso di tecniche quali "Activation", "Reflection" e "Dynamic Type Generation".
Dopo una sana validazione dell'argomento in ingresso alla funzione (che ci preserva da un uso imprevisto del metodo), procediamo a recuperare l'elenco dei provider configurati nella nostra applicazione. Ancora una volta non posso fare altro che ricordare che la lettura della configurazione, in questo caso gestita tramite un "mockup" (lo so...a volte sono un po' pigro), dovrebbe essere fatta eseguendo il caching del dato, mirato all'incremento delle prestazioni generali del sistema, ed evitando di sprecare inutilmente cicli di CPU.
Fatto questo, il nostro buon vecchio LINQ e una "Lambda Expression" (che ritengo essere la feature più "figa" della piattaforma .NET), ci permette di recuperare solo il provider richiesto durante l'invocazione del metodo "CreateProvider". A seguire, ancora una volta, è buona regola sollevare una bella e chiara eccezione nel caso in cui non sia possibile identificare l'oggetto richiesto, segnalando al collega che sta utilizzando il nostro metodo, che c'è qualche cosa che non funziona a dovere. Dico questo perchè la stringa con il nome del provider da istanziare non dovrebbe essere specificata a caso, ma essere il risultato di una selezione delle possibile alternative, anch'esse prelevate dalla configurazione applicativa.
L'invocazione del metodo interno "GenerateProviderType" permetterà di ottenere l'istanza del "type" della classe di implementazione reale dell'interfaccia "IFinderProvider". Ottenuto il tipo, il passaggio dallo stesso all'istanza è cosa da nulla, grazie al buon vecchio amico "Activator", compagno di mille battaglie.
Chiudiamo il cerchio con un'ultima verifica (la prudenza non è mai troppa) per testare effettivamente che l'oggetto appena creato è stato generato correttamente, e sia riconducibile ad un implementazione di "IFinderProvider". A tal proposito, è utile ricordare che la keyword "as" applicata ad un'istanza di un tipo non corrispondente a quello posto come secondo operatore ("IFinderProvider", in questo caso) determina l'impostazione della variabile istanziata a "null".
Quindi, se siamo stati bravi, diligenti e precisi, in uscita dal metodo avremo ottenuto quello che ci aspettavamo: un'istanza di "LocalFinderProvider", "ShareFinderProvider" oppure "WebServiceFinderProvider", semplicemente utilizzando una stringa di testo che identifica il provider desiderato.
Prima di chiudere, un brevissimo "excursus" sul funzionamento di "GenerateProviderType". Il metodo richiede in ingresso il "full type name" della classe di provider da istanziare; inoltre richiede che venga fornito il nome dell'assembly file che contiene la dichiarazione del type del provider stesso. Mentre il primo parametro è assolutamente essenziale al fine di una corretta generazione dell'oggetto, il secondo diventa rilevante solo nel momento in cui, all'avvio dell'applicazione, il type non è disponibile all'interno dell' AppDomain in esecuzione.
Questo vale a dire che ci sono casi in cui è necessario caricare "dinamicamente" un assembly nel contesto di esecuzione prima di poterlo effettivamente invocare ed utilizzare. Noi, per convenzione, abbiamo scelto che i file plugin che contengono i tipi "local", "share" e "webservice" fossero collocati nella stessa cartella di esecuzione dell'applicazione principale; ma chiaramente è una scelta "di comodo", che non impedisce di posizionare tali risorse in un luogo differente (magari anche questo stabilito da convenzione, oppure selezionabile in maniera interattiva), ed includerli nell'application contest a flusso operativo già in corso.
Inversion of Control e Dependecy Injection, come anticipato, sono tecniche di programmazione avanzata per la creazione di applicazioni modulari. Come per ogni tecnica possono essere molto utili se utilizzate con molta parsimonia e nelle situazioni giuste, evitando di cadere in tentazione di rendere "pluggabile" ogni minimo aspetto.
Quindi ricordarevi di pluggare la corretta implementazione di "IBrain" nella scatola cranica, prima di mettervi al lavoro. E in bocca al lupo...
M.
Il pezzo che manca a completare il puzzle è capire come dare la possibilità alla nostra applicazione di generare un'istanza di un particolare oggetto, che implementa l'interfaccia "IFinder", utilizzando una semplice stringa di testo. La struttura che permette la "magia" è la classe statica "FinderIoc", e il suoo metodo "CreateProvider". Come visto in precedenza, l'utilizzo della stessa è estremamente banale; l'implementazione nasconde qualche piccola insidia, dovendo fare uso di tecniche quali "Activation", "Reflection" e "Dynamic Type Generation".
Dopo una sana validazione dell'argomento in ingresso alla funzione (che ci preserva da un uso imprevisto del metodo), procediamo a recuperare l'elenco dei provider configurati nella nostra applicazione. Ancora una volta non posso fare altro che ricordare che la lettura della configurazione, in questo caso gestita tramite un "mockup" (lo so...a volte sono un po' pigro), dovrebbe essere fatta eseguendo il caching del dato, mirato all'incremento delle prestazioni generali del sistema, ed evitando di sprecare inutilmente cicli di CPU.
Fatto questo, il nostro buon vecchio LINQ e una "Lambda Expression" (che ritengo essere la feature più "figa" della piattaforma .NET), ci permette di recuperare solo il provider richiesto durante l'invocazione del metodo "CreateProvider". A seguire, ancora una volta, è buona regola sollevare una bella e chiara eccezione nel caso in cui non sia possibile identificare l'oggetto richiesto, segnalando al collega che sta utilizzando il nostro metodo, che c'è qualche cosa che non funziona a dovere. Dico questo perchè la stringa con il nome del provider da istanziare non dovrebbe essere specificata a caso, ma essere il risultato di una selezione delle possibile alternative, anch'esse prelevate dalla configurazione applicativa.
L'invocazione del metodo interno "GenerateProviderType" permetterà di ottenere l'istanza del "type" della classe di implementazione reale dell'interfaccia "IFinderProvider". Ottenuto il tipo, il passaggio dallo stesso all'istanza è cosa da nulla, grazie al buon vecchio amico "Activator", compagno di mille battaglie.
Chiudiamo il cerchio con un'ultima verifica (la prudenza non è mai troppa) per testare effettivamente che l'oggetto appena creato è stato generato correttamente, e sia riconducibile ad un implementazione di "IFinderProvider". A tal proposito, è utile ricordare che la keyword "as" applicata ad un'istanza di un tipo non corrispondente a quello posto come secondo operatore ("IFinderProvider", in questo caso) determina l'impostazione della variabile istanziata a "null".
Quindi, se siamo stati bravi, diligenti e precisi, in uscita dal metodo avremo ottenuto quello che ci aspettavamo: un'istanza di "LocalFinderProvider", "ShareFinderProvider" oppure "WebServiceFinderProvider", semplicemente utilizzando una stringa di testo che identifica il provider desiderato.
Prima di chiudere, un brevissimo "excursus" sul funzionamento di "GenerateProviderType". Il metodo richiede in ingresso il "full type name" della classe di provider da istanziare; inoltre richiede che venga fornito il nome dell'assembly file che contiene la dichiarazione del type del provider stesso. Mentre il primo parametro è assolutamente essenziale al fine di una corretta generazione dell'oggetto, il secondo diventa rilevante solo nel momento in cui, all'avvio dell'applicazione, il type non è disponibile all'interno dell' AppDomain in esecuzione.
Questo vale a dire che ci sono casi in cui è necessario caricare "dinamicamente" un assembly nel contesto di esecuzione prima di poterlo effettivamente invocare ed utilizzare. Noi, per convenzione, abbiamo scelto che i file plugin che contengono i tipi "local", "share" e "webservice" fossero collocati nella stessa cartella di esecuzione dell'applicazione principale; ma chiaramente è una scelta "di comodo", che non impedisce di posizionare tali risorse in un luogo differente (magari anche questo stabilito da convenzione, oppure selezionabile in maniera interattiva), ed includerli nell'application contest a flusso operativo già in corso.
Inversion of Control e Dependecy Injection, come anticipato, sono tecniche di programmazione avanzata per la creazione di applicazioni modulari. Come per ogni tecnica possono essere molto utili se utilizzate con molta parsimonia e nelle situazioni giuste, evitando di cadere in tentazione di rendere "pluggabile" ogni minimo aspetto.
Quindi ricordarevi di pluggare la corretta implementazione di "IBrain" nella scatola cranica, prima di mettervi al lavoro. E in bocca al lupo...
M.
Commenti
Posta un commento