Inyectando dependencias (DI) en aplicaciones c ++

8

Estoy jugando con la inyección de dependencia, pero no estoy seguro de hacerlo bien. Especialmente, no estoy seguro de cuál debería ser la forma correcta de crear clases con dependencias inyectadas.

Supongamos que tengo una clase A que crea la clase B. La clase B depende de la clase C y la clase C depende de la clase D. ¿Quién debería ser responsable de crear la clase D?

  1. Podría ser la clase A. Sin embargo, en un sistema grande, la clase A podría terminar creando y ensamblando una gran cantidad de objetos.

  2. Una clase de constructor separada que creará D, C y B. A usará esta clase de constructor.

  3. Alguna otra opción.

Además, leo mucho sobre los contenedores DI. Sin embargo, parece que no hay marcos importantes para C ++. Además, si entiendo correctamente, la DI puede realizarse bien incluso sin contenedores. ¿Estoy en lo correcto?

    
pregunta Erik Sapir 26.03.2014 - 20:29

3 respuestas

6
  

Supongamos que tengo una clase A que crea la clase B. La clase B depende de la clase C y la clase C depende de la clase D. ¿Quién debería ser responsable de crear la clase D?

Estás saltando pasos. Considere un conjunto de convenciones optimizadas para acoplamiento suelto y seguridad excepcional. Las reglas van así:

  • R1: si A contiene una B, entonces el constructor de A recibe una B completamente construida (es decir, no "dependencias de construcción de B"). De manera similar, si la construcción de B requiere una C, recibirá una C, no las dependencias de C.

  • R2: si se requiere una cadena completa de objetos para construir un objeto, la construcción encadenada se extrae / automatiza dentro de una fábrica (función o clase).

Código (std :: move call omitido por simplicidad):

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(C c) {return B{c}; };

En dicho sistema, "quien crea D" es irrelevante, porque cuando llamas a make_b, necesitas una C, no una D.

Código del cliente:

A a; // factory instance

// construct a B instance:
D d;
C c {d};
B = a.make_b(c);

Aquí, D es creado por el código del cliente. Naturalmente, si este código se repite más de una vez, puede extraerlo en una función (consulte R2 más arriba):

B make_b_from_d(D& d) // you should probably inject A instance here as well
{
    C c {d};
    A a;
    return a.make_b(c);
}

Existe una tendencia natural a omitir la definición de make_b (ignorar R1 ), y escribir el código directamente de esta manera:

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly

En este caso, tiene los siguientes problemas:

  • tienes código monolítico; Si se encuentra en una situación en el código del cliente donde necesita hacer una B a partir de una C existente, no puede usar make_b. Tendrá que escribir una nueva fábrica, o la definición de make_b, y todo el código del cliente usando el antiguo make_b.

  • Su visión de las dependencias se confunde cuando mira la fuente: Ahora, al mirar la fuente, puede pensar que necesita una instancia de D, cuando en realidad es posible que solo necesite una C.

Ejemplo:

void sub_optimal_solution(C& existent_c) {
    // you cannot create a B here using existent_C, because your A::make_b
    // takes a D parameter; B's construction doesn't actually need a D
    // but you cannot see that at all if you just have:
    // struct A { B make_b(D d); };
}
  • La omisión de struct A { B make_b(C c); } aumentará considerablemente el acoplamiento: ahora A necesita conocer las definiciones de B y C (en lugar de solo C). También tiene restricciones en cualquier código de cliente con A, B, C y D, impuestas en su proyecto porque omitió un paso en la definición de un método de fábrica ( R1 ) .

TLDR: En resumen, no pase la dependencia más externa a una fábrica, sino las más cercanas. Esto hace que su código sea robusto, fácilmente modificable y convierte la pregunta que usted formuló ("quién crea D") en una pregunta irrelevante para la implementación de make_b (porque make_b ya no recibe una D sino una dependencia más inmediata - C - y esto es inyectado como un parámetro de make_b).

    
respondido por el utnapistim 27.03.2014 - 16:58
3

Hay un marco DI para C ++ (aún en desarrollo AFAIK): Boost.DI .

Hay algunos comentarios útiles sobre el marco en reddit.

    
respondido por el Nemanja Trifunovic 22.01.2015 - 17:16
0
  

¿Quién debería ser responsable de crear la clase D?

Cada vez que aparece una pregunta como esa, indica una dependencia para inyectar . La idea general es "delegar" la responsabilidad fuera del objeto que parece tener un problema para saber cómo manejarlo.

Usando su ejemplo, ya que la clase A no parece tener suficiente "conocimiento" para averiguar cómo crear D, invierte el controle y expóngalo como una dependencia necesaria para la clase A, haciendo que requiera una factory que sabe cómo crear instancias de clase D.

    
respondido por el gnat 26.03.2014 - 20:41

Lea otras preguntas en las etiquetas