È meglio creare un singleton per accedere al container dell’unità o passarlo attraverso l’applicazione?

Sto immergendo il mio dito nell’uso di un framework IoC e ho scelto di usare Unity. Una delle cose che ancora non capisco è come risolvere gli oggetti più in profondità nell’applicazione. Sospetto che non abbia avuto la lampadina sul momento che lo renderà chiaro.

Quindi sto provando a fare qualcosa di simile al seguente nel codice psuedo’ish

void Workflow(IUnityContatiner contatiner, XPathNavigator someXml) { testSuiteParser = container.Resolve TestSuite testSuite = testSuiteParser.Parse(SomeXml) // Do some mind blowing stuff here } 

Quindi testSuiteParser.Parse esegue quanto segue

 TestSuite Parse(XPathNavigator someXml) { TestStuite testSuite = ??? // I want to get this from my Unity Container List aListOfNodes = DoSomeThingToGetNodes(someXml) foreach (XPathNavigator blah in aListOfNodes) { //EDIT I want to get this from my Unity Container TestCase testCase = new TestCase() testSuite.TestCase.Add(testCase); } } 

Posso vedere tre opzioni:

  1. Crea un Singleton per memorizzare il mio container di unità a cui posso accedere ovunque. Non sono davvero un fan di questo approccio. Aggiungere una dipendenza del genere per usare una struttura di iniezione delle dipendenze sembra un po ‘strana.
  2. Passare IUnityContainer alla mia class TestSuiteParser e ogni figlio di esso (supponiamo che sia n livelli profondi o in realtà a circa 3 livelli di profondità). Passare IUnityContainer in giro ovunque sembra strano. Potrei aver bisogno di superarlo.
  3. Avere il momento della lampadina nel modo giusto per usare Unity. Sperando che qualcuno possa aiutare a colpire l’interruttore.

[EDIT] Una delle cose su cui non sono stato chiaro è che voglio creare una nuova istanza di test case per ogni iterazione dell’istruzione foreach. L’esempio precedente deve analizzare una configurazione della suite di test e popolare una raccolta di oggetti del caso di test

L’approccio corretto a DI consiste nell’utilizzare l’ Iniezione del Costruttore o un altro modello DI (ma l’Iniezione del Costruttore è il più comune) per iniettare le dipendenze nel consumatore, indipendentemente dal contenitore DI .

Nel tuo esempio, sembra che tu abbia bisogno delle dipendenze TestSuite e TestCase , quindi la tua class TestSuiteParser dovrebbe annunciare staticamente che richiede queste dipendenze chiedendole tramite il suo costruttore (solo):

 public class TestSuiteParser { private readonly TestSuite testSuite; private readonly TestCase testCase; public TestSuiteParser(TestSuite testSuite, TestCase testCase) { if(testSuite == null) { throw new ArgumentNullException(testSuite); } if(testCase == null) { throw new ArgumentNullException(testCase); } this.testSuite = testSuite; this.testCase = testCase; } // ... } 

Si noti come la combinazione della parola chiave readonly e della clausola di guardia protegga gli invarianti della class, garantendo che le dipendenze siano disponibili per qualsiasi istanza di TestSuiteParser creata correttamente.

Ora puoi implementare il metodo Parse in questo modo:

 public TestSuite Parse(XPathNavigator someXml) { List aListOfNodes = DoSomeThingToGetNodes(someXml) foreach (XPathNavigator blah in aListOfNodes) { this.testSuite.TestCase.Add(this.testCase); } } 

(Tuttavia, ho il sospetto che potrebbe esserci più di una TestCase coinvolta, nel qual caso potreste voler iniettare una Fabbrica Astratta invece di una singola TestCase.)

Dalla tua Root di composizione , puoi configurare Unity (o qualsiasi altro contenitore):

 container.RegisterType(); container.RegisterType(); container.RegisterType(); var parser = container.Resolve(); 

Quando il contenitore risolve TestSuiteParser, comprende il pattern Injection del costruttore, quindi esegue il collegamento automatico dell’istanza con tutte le sue dipendenze richieste.

La creazione di un container Singleton o il passaggio del contenitore sono solo due varianti dell’anti-pattern del localizzatore di servizi , quindi non lo consiglierei.

Sono nuovo di Dependency Injection e ho anche avuto questa domanda. Stavo faticando a pensare a DI, principalmente perché mi stavo concentrando sull’applicazione di DI alla sola class su cui stavo lavorando e una volta aggiunte le dipendenze al costruttore, ho cercato immediatamente di trovare un modo per ottenere l’unità contenitore per i luoghi in cui questa class doveva essere istanziata in modo che potessi chiamare il metodo Resolve sulla class. Di conseguenza, stavo pensando di rendere il contenitore di unità globalmente disponibile come statico o di inserirlo in una class singleton.

Ho letto le risposte qui e non ho davvero capito cosa veniva spiegato. Ciò che alla fine mi ha aiutato a “capirlo” era questo articolo:

http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

E questo paragrafo in particolare è stato il momento della “lampadina”:

“Il 99% del tuo codice base non dovrebbe avere alcuna conoscenza del tuo contenitore IoC. È solo la root class o bootstrapper che usa il container e anche in quel caso, una singola chiamata di risoluzione è tutto ciò che è tipicamente necessario per build il tuo grafico di dipendenza e avviare il domanda o richiesta. “

Questo articolo mi ha aiutato a capire che in realtà non devo accedere al contenitore di unità su tutta l’applicazione, ma solo alla radice dell’applicazione. Quindi devo applicare il principio DI ripetutamente fino alla class radice dell’applicazione.

Spero che questo aiuti gli altri che sono confusi come me! 🙂

Non dovresti davvero usare il tuo contenitore direttamente in molti punti della tua applicazione. Dovresti prendere tutte le tue dipendenze dal costruttore e non raggiungerle dai tuoi metodi. L’esempio potrebbe essere qualcosa del genere:

 public class TestSuiteParser : ITestSuiteParser { private TestSuite testSuite; public TestSuitParser(TestSuit testSuite) { this.testSuite = testSuite; } TestSuite Parse(XPathNavigator someXml) { List aListOfNodes = DoSomeThingToGetNodes(someXml) foreach (XPathNavigator blah in aListOfNodes) { //I don't understand what you are trying to do here? TestCase testCase = ??? // I want to get this from my Unity Container testSuite.TestCase.Add(testCase); } } } 

E poi lo fai allo stesso modo su tutta l’applicazione. Ovviamente, a un certo punto dovrai risolvere qualcosa. Ad esempio, in asp.net mvc questo posto si trova nella fabbrica del controllore. Questa è la fabbrica che crea il controller. In questo stabilimento utilizzerai il tuo contenitore per risolvere i parametri del tuo controller. Ma questo è solo un posto nell’intera applicazione (probabilmente altri posti quando si fanno cose più avanzate).

C’è anche un bel progetto chiamato CommonServiceLocator . Questo è un progetto che ha un’interfaccia condivisa per tutti i popolari contenitori di ioc in modo da non avere una dipendenza da un contenitore specifico.

Se solo uno può avere un “ServiceLocator” che viene passato attorno ai costruttori di servizi, ma in qualche modo riesce a “dichiarare” le dipendenze previste della class in cui viene iniettato (cioè non hide le dipendenze) … in quel modo, tutto (? ) le obiezioni al modello di localizzazione del servizio possono essere messe a rest.

 public class MyBusinessClass { public MyBusinessClass(IServiceResolver locator) { //keep the resolver for later use } } 

Purtroppo, quanto sopra ovviamente sarà sempre e solo nei miei sogni, dato che c # proibisce parametri generici variabili (ancora), quindi aggiungere manualmente una nuova interfaccia generica ogni volta che si ha bisogno di un parametro generico aggiuntivo, sarebbe poco maneggevole.

Se d’altra parte, quanto sopra potrebbe essere raggiunto nonostante la limitazione di c # nel modo seguente …

 public class MyBusinessClass { public MyBusinessClass(IServiceResolver>> locator) { //keep the resolver for later use } } 

In questo modo, è sufficiente eseguire una digitazione aggiuntiva per ottenere la stessa cosa. Ciò di cui non sono ancora sicuro è se, dato il corretto design della class TArg (presumo che venga impiegata un’eredità intelligente per consentire l’annidamento infinito di parametri TArg generici), i contenitori DI saranno in grado di risolvere correttamente l’ IServiceResolver . L’idea, in definitiva, è di passare semplicemente alla stessa implementazione di IServiceResolver indipendentemente dalla dichiarazione generica trovata nel costruttore della class in cui viene iniettata.