Esiste un modello per inizializzare gli oggetti creati tramite un contenitore DI

Sto cercando di ottenere Unity per gestire la creazione dei miei oggetti e voglio avere alcuni parametri di inizializzazione che non sono noti fino al runtime:

Al momento l’unico modo in cui potrei pensare a come farlo è avere un metodo Init sull’interfaccia.

interface IMyIntf { void Initialize(string runTimeParam); string RunTimeParam { get; } } 

Quindi per usarlo (in Unity) lo farei:

 var IMyIntf = unityContainer.Resolve(); IMyIntf.Initialize("somevalue"); 

In questo scenario, il parametro runTimeParam viene determinato in fase di esecuzione in base all’input dell’utente. Il caso banale qui semplicemente restituisce il valore di runTimeParam ma in realtà il parametro sarà qualcosa come il nome del file e il metodo di inizializzazione farà qualcosa con il file.

Ciò crea una serie di problemi, vale a dire che il metodo di Initialize è disponibile nell’interfaccia e può essere chiamato più volte. L’impostazione di un flag nell’implementazione e l’eccezione di lancio sulla chiamata ripetuta a Initialize sembra molto clunky.

Nel momento in cui IMyIntf mia interfaccia, non voglio sapere nulla dell’implementazione di IMyIntf . Quello che voglio, però, è la consapevolezza che questa interfaccia ha bisogno di determinati parametri di inizializzazione una tantum. C’è un modo per annotare in qualche modo (attributi?) L’interfaccia con queste informazioni e passare quelle al framework quando l’object viene creato?

Modifica: Descritto l’interfaccia un po ‘di più.

Qualsiasi luogo in cui sia necessario un valore di runtime per build una particolare dipendenza, Abstract Factory è la soluzione.

Avere Inizializzare i metodi sulle interfacce puzza di un’Astrazione Leaky .

Nel tuo caso, direi che dovresti modellare l’interfaccia di IMyIntf su come devi usarlo, non su come intendi creare le sue implementazioni. Questo è un dettaglio di implementazione.

Quindi l’interfaccia dovrebbe essere semplicemente:

 public interface IMyIntf { string RunTimeParam { get; } } 

Ora definisci la Fabbrica astratta:

 public interface IMyIntfFactory { IMyIntf Create(string runTimeParam); } 

Ora puoi creare un’implementazione concreta di IMyIntfFactory che crea istanze concrete di IMyIntf come questa:

 public class MyIntf : IMyIntf { private readonly string runTimeParam; public MyIntf(string runTimeParam) { if(runTimeParam == null) { throw new ArgumentNullException("runTimeParam"); } this.runTimeParam = runTimeParam; } public string RunTimeParam { get { return this.runTimeParam; } } } 

Si noti come questo ci consenta di proteggere gli invarianti della class mediante la parola chiave readonly . Non male sono necessari i metodi di inizializzazione.

Un’implementazione IMyIntfFactory può essere semplice come questa:

 public class MyIntfFactory : IMyIntfFactory { public IMyIntf Create(string runTimeParam) { return new MyIntf(runTimeParam); } } 

In tutti i tuoi consumatori in cui hai bisogno di un’istanza di IMyIntf , devi semplicemente prendere una dipendenza da IMyIntfFactory richiedendola tramite Constructor Injection .

Qualsiasi contenitore DI valga la pena salare sarà in grado di colbind IMyIntfFactory un’istanza IMyIntfFactory per te se lo registri correttamente.

Di solito quando si verifica questa situazione, è necessario rivisitare la propria progettazione e determinare se si stanno mescolando oggetti di stato / dati con i propri servizi puri. Nella maggior parte dei casi (non tutti), dovrai tenere separati questi due tipi di oggetti.

Se hai bisogno di un parametro specifico del contesto passato nel costruttore, un’opzione è creare un factory che risolva le dipendenze del servizio tramite il costruttore e prende il tuo parametro run-time come parametro del metodo Create () (o Genera ( ), Build () o qualunque sia il nome dei metodi di fabbrica).

Avere setter o un metodo Initialize () in genere si pensa che sia una ctriggers progettazione, in quanto è necessario “ricordare” di chiamarli e assicurarsi che non si aprano troppo dello stato della propria implementazione (cioè cosa è impedire a qualcuno di venire -Calling inizializza o il setter?).

Ho anche incontrato questa situazione alcune volte in ambienti in cui sto creando dynamicmente oggetti ViewModel basati su oggetti Model (delineati molto bene da questo altro post Stackoverflow ).

Mi è piaciuto come l’ estensione Ninject che ti permette di creare dynamicmente le fabbriche in base alle interfacce:

Bind().ToFactory();

Non sono riuscito a trovare alcuna funzionalità simile direttamente in Unity ; così ho scritto la mia estensione a IUnityContainer che ti permette di registrare le fabbriche che creeranno nuovi oggetti basati su dati provenienti da oggetti esistenti, essenzialmente mappando da una gerarchia di tipi a una gerarchia di tipi diversi: UnityMappingFactory @ GitHub

Con un objective di semplicità e leggibilità, ho trovato un’estensione che consente di specificare direttamente i mapping senza dichiarare singole classi o interfacce di fabbrica (un risparmio in tempo reale). Basta aggiungere i mapping proprio dove si registrano le classi durante il normale processo di avvio …

 //make sure to register the output... container.RegisterType(); container.RegisterType(); //define the mapping between different class hierarchies... container.RegisterFactory() .AddMap() .AddMap(); 

Quindi dichiarare semplicemente l’interfaccia di fabbrica di mapping nel costruttore per CI e usare il suo metodo Create ()

 public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { } public TextWidgetViewModel(ITextWidget widget) { } public ContainerViewModel(object data, IFactory factory) { IList children = new List(); foreach (IWidget w in data.Widgets) children.Add(factory.Create(w)); } 

Come bonus aggiuntivo, eventuali dipendenze aggiuntive nel costruttore delle classi mappate verranno risolte anche durante la creazione dell’object.

Ovviamente, questo non risolverà tutti i problemi, ma finora mi è servito molto bene, quindi ho pensato di condividerlo. C’è più documentazione sul sito del progetto su GitHub.

Non posso rispondere con una terminologia specifica di Unity, ma sembra che tu stia solo imparando sull’iniezione di dipendenza. In tal caso, vi invito a leggere la guida per l’utente breve, chiara e ricca di informazioni per Ninject .

Questo ti guiderà attraverso le varie opzioni che hai quando usi DI e come tenere conto dei problemi specifici che dovrai affrontare lungo il percorso. Nel tuo caso, molto probabilmente vorrai usare il contenitore DI per istanziare i tuoi oggetti e fare in modo che quell’object ottenga un riferimento a ciascuna delle sue dipendenze attraverso il costruttore.

La procedura dettagliata descrive inoltre come annotare metodi, proprietà e parametri utilizzando gli attributi per distinguerli in fase di runtime.

Anche se non usi Ninject, la procedura dettagliata ti fornirà i concetti e la terminologia della funzionalità adatta al tuo scopo e dovresti essere in grado di associare tale conoscenza a Unity o ad altri framework DI (o convincerti a dare una prova a Ninject) .

Penso di averlo risolto e sembra piuttosto sano, quindi deve essere a metà :))

Ho diviso IMyIntf in un’interfaccia “getter” e “setter”. Così:

 interface IMyIntf { string RunTimeParam { get; } } interface IMyIntfSetter { void Initialize(string runTimeParam); IMyIntf MyIntf {get; } } 

Quindi l’implementazione:

 class MyIntfImpl : IMyIntf, IMyIntfSetter { string _runTimeParam; void Initialize(string runTimeParam) { _runTimeParam = runTimeParam; } string RunTimeParam { get; } IMyIntf MyIntf {get {return this;} } } //Unity configuration: //Only the setter is mapped to the implementation. container.RegisterType(); //To retrieve an instance of IMyIntf: //1. create the setter IMyIntfSetter setter = container.Resolve(); //2. Init it setter.Initialize("someparam"); //3. Use the IMyIntf accessor IMyIntf intf = setter.MyIntf; 

IMyIntfSetter.Initialize() può ancora essere chiamato più volte, ma utilizzando i bit del paradigma Service Locator è ansible eseguire il wrapping abbastanza bene in modo che IMyIntfSetter sia quasi un’interfaccia interna distinta da IMyIntf .