¿Es mejor crear un singleton para acceder al contenedor de unity 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 objetos más profundos en la aplicación. Sospecho que no he tenido la bombilla encendida en el momento que lo aclare.

Así que estoy tratando de hacer algo como lo siguiente en pseudo ish código

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

Así que el testSuiteParser.Parse hace lo siguiente

TestSuite Parse(XPathNavigator someXml)
{
    TestStuite testSuite = ??? // I want to get this from my Unity Container
    List<XPathNavigator> 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. Crear un Singleton para almacenar mi contenedor unity al que puedo acceder en cualquier lugar. Realmente no soy un fan de este enfoque. Agregar una dependencia como esa para usar un marco de inyección de dependencias parece un poco extraño.
  2. Pase el IUnityContainer a mi clase TestSuiteParser y a cada hijo de ella (asuma que es n niveles de profundidad o en realidad alrededor de 3 niveles de profundidad). Pasar IUnityContainer por todas partes parece extraño. Puede que tenga que superar esto.
  3. Tener el momento de la bombilla en el la forma correcta de usar Unity. Espero que alguien pueda ayudar a mover el interruptor.
[2] [EDITAR]] Una de las cosas que no estaba claro es que quiero crear una nueva instancia de caso de prueba para cada iteración de la sentencia foreach. El ejemplo anterior necesita analizar la configuración de un conjunto de pruebas y rellenar una colección de objetos de casos de prueba
Author: btlog, 2010-03-05

4 answers

El enfoque correcto para DI es usar 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 del Contenedor DI.

En su ejemplo, parece que requiere las dependencias TestSuite y TestCase, por lo que su clase TestSuiteParser debería anunciar estáticamente que requiere estas dependencias pidiéndolas a través de su (solo) 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 readonlyy la Cláusula Guard protege los invariantes de la clase, asegurando que las dependencias estarán disponibles para cualquier instancia creada con éxito de TestSuiteParser.

Ahora puede implementar el método Parse de esta manera:

public TestSuite Parse(XPathNavigator someXml) 
{ 
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml) 

    foreach (XPathNavigator blah in aListOfNodes) 
    { 
        this.testSuite.TestCase.Add(this.testCase); 
    }  
} 

(sin embargo, sospecho que puede haber más de un caso de prueba involucrado, en cuyo caso es posible que desee inyectar una Fábrica Abstracta en lugar de una sola TestCase.)

Desde su Raíz de composición , puede configurar Unity (o cualquier otro contenedor):

container.RegisterType<TestSuite, ConcreteTestSuite>();
container.RegisterType<TestCase, ConcreteTestCase>();
container.RegisterType<TestSuiteParser>();

var parser = container.Resolve<TestSuiteParser>();

Cuando el contenedor resuelve TestSuiteParser, entiende el patrón de inyección del Constructor, por lo que Conecta 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.

 50
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:11

Soy nuevo en Dependency Injection y también tenía esta pregunta. Yo estaba luchando para conseguir mi mente alrededor de DI, principalmente porque me estaba centrando en aplicar DI solo a la clase en la que estaba trabajando y una vez que había añadido las dependencias al constructor, inmediatamente traté de encontrar alguna manera de conseguir el contenedor de unity a los lugares donde esta clase necesitaba ser instanciada para que pudiera llamar al método Resolve en la clase. Como resultado, estaba pensando en hacer el contenedor unity disponible globalmente como estático o envolviéndolo en una clase singleton.

Leí las respuestas aquí y realmente no entendí lo que se estaba explicando. Lo que finalmente me ayudó a" conseguirlo " 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":

"99% de su base de código no debe tener conocimiento de su contenedor IoC. Es solo la clase raíz o bootstrapper que usa el contenedor e incluso entonces, una sola llamada de resolución es todo lo que normalmente es necesario para construir su gráfico de dependencias e iniciar la aplicación o solicitud."

Este artículo me ayudó a entender que en realidad no debo acceder al contenedor de unity en toda la aplicación, sino solo en la raíz de la aplicación. Así que debo aplicar el principio DI repetidamente hasta la clase raíz de la aplicación.

Espero que esto ayude ¡otros que están tan confundidos como yo! :)

 12
Author: BruceHill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-17 23:31:37

Realmente no debería necesitar usar su contenedor directamente en muchos lugares de su aplicación. Debes tomar todas tus dependencias en el constructor y no alcanzarlas desde tus métodos. Que 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<XPathNavigator> 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. Usted, por supuesto, en algún momento tendrá que resolver algo. En asp.net mvc por ejemplo, este lugar está en la fábrica del controlador. Esa es la fábrica que crea el controlador. En esta fábrica utilizarás tu contenedor para resolver los parámetros de tu controlador. Pero esto 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 de un contenedor específico.

 4
Author: Mattias Jakobsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-05 12:22:46

Si solo uno pudiera tener un "ServiceLocator" que se pasa alrededor de los constructores de servicios, pero de alguna manera se las arregla para "Declarar" las dependencias previstas de la clase en la que se está inyectando (es decir, no ocultar las dependencias)...de esa manera, todos(?) las objeciones al patrón de localización de servicios pueden ser dejadas de lado.

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<Dependency1, Dependency2, Dependency3> 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 genéricos variables (todavía), por lo que agregar manualmente una nueva interfaz genérica cada vez que se 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<TArg<Dependency1, TArg<Dependency2, TArg<Dependency3>>> locator)
    {
        //keep the resolver for later use
    }
}

De esta manera, solo se necesita escribir extra 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 empleará una herencia inteligente para permitir el anidamiento infinito de los parámetros genéricos 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 declaración genérica encontrada en el constructor de la clase que se inyecta.

 0
Author: Dantte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-30 12:04:07