¿Es mejor crear un singleton para acceder al contenedor de la unidad o pasarlo a través de la aplicación?

Estoy sumergiendo mi dedo del pie en el uso de un marco de IoC y he elegido utilizar Unity. Una de las cosas que todavía no entiendo completamente es cómo resolver los objetos más profundamente en la aplicación. Sospecho que simplemente no he tenido la bombilla en el momento que lo dejará en claro.

Así que estoy tratando de hacer algo como lo siguiente en el código psuedoish

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

Entonces testSuiteParser.Parse hace lo siguiente

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

Puedo ver tres opciones:

  1. Crea un Singleton para almacenar mi contenedor de unidad al que puedo acceder desde cualquier lugar. Realmente no soy un fan de este enfoque. Agregar una dependencia como esa para usar un marco de dependency injection parece un poco extraño.
  2. Pase el IUnityContainer a mi clase TestSuiteParser y a cada uno de sus elementos secundarios (suponiendo que tiene n niveles profundos o, en realidad, unos 3 niveles de profundidad). Pasar IUnityContainer por todos lados parece extraño. Quizás necesite superar esto.
  3. Tenga el momento de la bombilla en el camino correcto para usar Unity. Esperando que alguien pueda ayudar a presionar el interruptor.

[EDIT] Una de las cosas en las que no estaba claro es que quiero crear una nueva instancia de caso de prueba para cada iteración de la instrucción foreach. El ejemplo anterior necesita analizar una configuración de conjunto de pruebas y rellenar una colección de objetos de caso de prueba

El enfoque correcto para DI es usar la Inyección de Constructor u otro patrón DI (pero la Inyección de Constructor es la más común) para inyectar las dependencias en el consumidor, independientemente de Contenedor DI .

En su ejemplo, parece que necesita las dependencias TestSuite y TestCase , por lo que su clase TestSuiteParser debe anunciar estáticamente que requiere estas dependencias solicitándolas a través de su (único) constructor:

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

Observe cómo la combinación de la palabra clave de readonly y la cláusula Guard protege las invariantes de la clase, asegurando que las dependencias estarán disponibles para cualquier instancia de TestSuiteParser creada con éxito.

Ahora puede implementar el método Parse así:

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

(Sin embargo, sospecho que puede haber más de un TestCase involucrado, en cuyo caso es posible que desee inyectar una Abstract Factory en lugar de un solo TestCase).

Desde su Composition Root , puede configurar Unity (o cualquier otro contenedor):

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

Cuando el contenedor resuelve TestSuiteParser, comprende el patrón de Inyección de Constructor, por lo que enlaza automáticamente la instancia con todas sus dependencias requeridas.

Crear un contenedor Singleton o pasar el contenedor son solo dos variaciones del anti-patrón del Localizador de Servicios , así que no lo recomendaría.

Soy nuevo en Dependency Injection y también tuve esta pregunta. Estaba luchando por no pensar en DI, principalmente porque me estaba enfocando en aplicar DI a la única clase en la que estaba trabajando y una vez que agregué las dependencias al constructor, inmediatamente traté de encontrar la manera de lograr la unidad. contenedor a los lugares donde esta clase necesitaba ser instanciada para poder llamar al método Resolve en la clase. Como resultado, estaba pensando en hacer el contenedor de la unidad disponible globalmente como estático o envolverlo en una clase singleton.

Leí las respuestas aquí y realmente no entendí lo que se explicaba. Lo que finalmente me ayudó a “entenderlo” fue este artículo:

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

Y este párrafo en particular fue el momento de la “bombilla”:

“El 99% de la base de códigos no debe tener conocimiento de su contenedor de IoC. Solo es la clase raíz o el progtwig de arranque el que usa el contenedor e incluso entonces, una única llamada de resolución es todo lo que normalmente es necesario para construir su gráfico de dependencia y comenzar el solicitud o solicitud “.

Este artículo me ayudó a comprender que, en realidad, no debo acceder al contenedor de la unidad en toda la aplicación, sino solo en la raíz de la aplicación. De modo que debo aplicar el principio DI varias veces hasta la clase raíz de la aplicación.

Espero que esto ayude a otros que están tan confundidos como yo. 🙂

En realidad, no debería necesitar usar su contenedor directamente en muchos lugares de su aplicación. Deberías tomar todas tus dependencias en el constructor y no llegar a ellas desde tus métodos. Tu ejemplo podría ser algo como esto:

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

Y luego lo haces de la misma manera en toda la aplicación. Por supuesto, en algún momento tendrás que resolver algo. En asp.net mvc, por ejemplo, este lugar está en la fábrica de controladores. Esa es la fábrica que crea el controlador. En esta fábrica, usará su contenedor para resolver los parámetros de su controlador. Pero este es solo un lugar en toda la aplicación (probablemente algunos lugares más cuando haces cosas más avanzadas).

También hay un buen proyecto llamado CommonServiceLocator . Este es un proyecto que tiene una interfaz compartida para todos los contenedores ioc populares para que no tenga una dependencia en un contenedor específico.

Si solo uno pudiera tener un “ServiceLocator” que se pasa alrededor de los constructores del servicio, pero de alguna manera logra “Declarar” las dependencias previstas de la clase en la que se está inyectando (es decir, no ocultar las dependencias) … de esa manera, todo (? ) las objeciones al patrón del localizador de servicios pueden dejarse de lado.

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

Lamentablemente, lo anterior obviamente solo existirá en mis sueños, ya que c # prohíbe los parámetros generics variables (aún), por lo que agregar manualmente una nueva Interfaz genérica cada vez que uno necesita un parámetro genérico adicional, sería difícil de manejar.

Si, por otro lado, lo anterior podría lograrse a pesar de la limitación de c # de la siguiente manera …

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

De esta manera, uno solo necesita hacer tipeo adicional para lograr lo mismo. De lo que todavía no estoy seguro es si, dado el diseño adecuado de la clase TArg (supongo que se utilizará una herencia inteligente para permitir el anidamiento infinito de los parámetros generics de TArg ), los contenedores DI podrán resolver el IServiceResolver correctamente. La idea, en última instancia, es simplemente pasar la misma implementación de IServiceResolver sin importar la statement genérica encontrada en el constructor de la clase a la que se inyecta.