Véndeme en contenedores IoC, por favor

17

He visto varios recomendar el uso de contenedores IoC en el código. La motivación es simple. Tome el siguiente código inyectado de dependencia:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest(
        std::auto_ptr<Dependency> d = std::auto_ptr<Dependency>(new ConcreteDependency)
    ) : d_(d)
    {
    }
};


TEST(UnitUnderTest, Example)
{
    std::auto_ptr<Dependency> dep(new MockDependency);
    UnitUnderTest uut(dep);
    //Test here
}

Dentro:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest()
    {
        d_.reset(static_cast<Dependency *>(IocContainer::Get("Dependency")));
    }
};


TEST(UnitUnderTest, Example)
{
    UnitUnderTest uut;
    //Test here
}

//Config for IOC container normally
<Dependency>ConcreteDependency</Dependency>

//Config for IOC container for testing
<Dependency>MockDependency</Dependency>

(Lo anterior es un ejemplo hipotético de C ++, por supuesto)

Si bien estoy de acuerdo en que esto simplifica la interfaz de la clase al eliminar el parámetro del constructor de dependencias, creo que la cura es peor que la enfermedad por un par de razones. Primero, y esto es muy importante para mí, esto hace que su programa dependa de un archivo de configuración externo. Si necesita una implementación binaria única, simplemente no puede usar este tipo de contenedores. El segundo problema es que la API ahora es débil y peor, tipada en cadena . La evidencia (en este ejemplo hipotético) es el argumento de cadena del contenedor de IoC y el reparto del resultado.

Entonces, ¿hay otros beneficios de usar este tipo de contenedores o simplemente no estoy de acuerdo con los que recomiendan los contenedores?

Billy ONeal
fuente
1
El código de muestra parece un mal ejemplo de una implementación de IoC. Solo estoy familiarizado con C #, pero seguramente también hay una forma de tener la inyección del constructor y la configuración programática en C ++.
rmac

Respuestas:

12

En una aplicación grande con muchas capas y muchas partes móviles, son los inconvenientes los que comienzan a parecer bastante menores en comparación con las ventajas.

El contenedor "simplifica la interfaz" de la clase, pero lo hace de una manera muy importante. El contenedor es una solución al problema que crea la inyección de dependencias, que es la necesidad de pasar dependencias por todo el lugar, a través de gráficos de objetos y áreas funcionales. Aquí tiene un pequeño ejemplo que tiene una dependencia: ¿qué pasa si este objeto tiene tres dependencias y los objetos que dependen de él tienen múltiples objetos que dependen de ellos , y así sucesivamente? Sin un contenedor, los objetos en la parte superior de esas cadenas de dependencia terminan siendo responsables de realizar un seguimiento de todas las dependencias en toda la aplicación.

También hay diferentes tipos de contenedores. No todos están escritos en cadena y no todos requieren archivos de configuración.

nlawalker
fuente
3
Sin embargo, un gran proyecto hace que los problemas que ya he mencionado peor . Si el proyecto es grande, el archivo de configuración se convierte en un enorme imán de dependencia, y se vuelve aún más fácil tener un error tipográfico que conduzca a un comportamiento indefinido en tiempo de ejecución. (es decir, tener un error tipográfico en el archivo de configuración provocaría que la conversión provoque un comportamiento indefinido si se devuelve el tipo incorrecto) En cuanto a las dependencias de las dependencias, aquí no importan. O el constructor se encarga de hacerlos, o la prueba se burlará de la dependencia de nivel superior.
Billy ONeal
... Continúa ... Cuando el programa no se está probando, la dependencia concreta predeterminada se instancia en cada paso. Otros tipos de contenedores, ¿tienes algunos ejemplos?
Billy ONeal
44
TBH No tengo mucha experiencia con implementaciones de DI, pero eche un vistazo a MEF en .NET, que puede ser pero no tiene que ser tipeado en cadena, no tiene archivo de configuración y las implementaciones concretas de interfaces están "registradas" en las clases en sí. Por cierto, cuando dices: "el constructor se encarga de hacerlos", si eso es lo que estás haciendo (también conocido como "inyección del constructor del hombre pobre"), entonces no necesitas un contenedor. La ventaja de un contenedor sobre hacerlo es que el contenedor centraliza la información sobre todas esas dependencias.
nlawalker
+1 para ese último comentario - la última oración allí es la respuesta :)
Billy ONeal
1
Gran respuesta que me ayudó a entender todo el punto. El punto no es solo simplificar la plantilla, sino permitir que los elementos en la parte superior de la cadena de dependencia sean tan ingenuos como los elementos en la parte inferior.
Christopher Berman
4

El marco Guice IoC de Java no se basa en un archivo de configuración sino en un código de configuración . Esto significa que la configuración es un código que no difiere del código que constituye su aplicación real, y se puede refactorizar, etc.

Creo que Guice es el marco de Java IoC que finalmente consiguió la configuración correcta.


fuente
¿Hay algún ejemplo similar para C ++?
Billy ONeal
@ Billy, no estoy lo suficientemente familiarizado con C ++ para poder decir.
0

Esta gran respuesta SO de Ben Scheirman detalla algunos ejemplos de código en C #; Algunas ventajas de los contenedores IoC (DI) son:

  • Su cadena de dependencias puede anidarse, y rápidamente se vuelve difícil de manejar para conectarlas manualmente.
  • Permite el uso de la programación orientada a aspectos .
  • Transacciones de bases de datos declarativas y anidadas.
  • Unidad de trabajo declarativa y anidada.
  • Inicio sesión.
  • Condiciones previas / posteriores (diseño por contrato).
dodgy_coder
fuente
1
No veo cómo ninguna de esas cosas se puede implementar con una simple inyección de constructor.
Billy ONeal
Con un buen diseño, la inyección del constructor u otro método puede ser suficiente para usted; Las ventajas de estos contenedores IoC pueden ser solo en casos específicos. El ejemplo dado con INotifyPropertyChanged en un proyecto WPF fue uno de esos ejemplos.
dodgy_coder