Tengo una pregunta simple, y ni siquiera estoy seguro de que tenga una respuesta, pero intentemos. Estoy codificando en C ++ y usando la inyección de dependencia para evitar el estado global. Esto funciona bastante bien, y no corro comportamientos inesperados / indefinidos muy a menudo.
Sin embargo, me doy cuenta de que, a medida que mi proyecto crece, escribo mucho código que considero repetitivo. Peor aún: el hecho de que haya más código repetitivo que el código real hace que a veces sea difícil de entender.
Nada es mejor que un buen ejemplo, así que vamos:
Tengo una clase llamada TimeFactory que crea objetos Time.
Para más detalles (no estoy seguro de que sea relevante): los objetos de tiempo son bastante complejos porque el tiempo puede tener diferentes formatos, y la conversión entre ellos no es lineal ni directa. Cada "Hora" contiene un Sincronizador para manejar las conversiones, y para asegurarse de que tengan el mismo sincronizador correctamente inicializado, utilizo un TimeFactory. TimeFactory tiene solo una instancia y es de aplicación amplia, por lo que calificaría para singleton pero, debido a que es mutable, no quiero convertirlo en singleton
En mi aplicación, muchas clases necesitan crear objetos de tiempo. Algunas veces esas clases están profundamente anidadas.
Digamos que tengo una clase A que contiene instancias de la clase B, y así sucesivamente hasta la clase D. La clase D necesita crear objetos de tiempo.
En mi ingenua implementación, paso el TimeFactory al constructor de la clase A, que lo pasa al constructor de la clase B y así sucesivamente hasta la clase D.
Ahora, imagine que tengo un par de clases como TimeFactory y un par de jerarquías de clases como la anterior: pierdo toda la flexibilidad y legibilidad que se supone que debo usar con la inyección de dependencia.
Estoy empezando a preguntarme si no hay una falla de diseño importante en mi aplicación ... ¿O es un mal necesario usar la inyección de dependencia?
Qué piensas ?
fuente
Respuestas:
Parece que su
Time
clase es un tipo de datos muy básico que debe pertenecer a la "infraestructura general" de su aplicación. DI no funciona bien para tales clases. Piense en lo que significa si una clase como sestring
tuviera que inyectar en cada parte del código que usa cadenas, y necesitaría usar astringFactory
como la única posibilidad de crear nuevas cadenas: la legibilidad de su programa disminuiría en un orden de magnitud.Entonces, mi sugerencia: no use DI para tipos de datos generales como
Time
. Escriba pruebas unitarias para laTime
clase en sí, y cuando haya terminado, úsela en todas partes de su programa, como lastring
clase, o unavector
clase o cualquier otra clase de la biblioteca estándar. Use DI para componentes que realmente deberían estar desacoplados uno del otro.fuente
Time
y las otras partes de su programa. Por lo tanto, podría aceptar también aceptar un acoplamiento estrechoTimeFactory
. Sin embargo, lo que evitaría es tener un únicoTimeFactory
objeto global con un estado (por ejemplo, una información local o algo así), que podría causar efectos secundarios desagradables en su programa y hacer que las pruebas generales y la reutilización sean muy difíciles. Hazlo apátrida o no lo uses como un singleton.Time
yTimeFactory
del grado de "capacidad de evolución" que necesita para futuras extensiones relacionadasTimeFactory
, etc.¿Qué quiere decir con "pierdo toda la flexibilidad y legibilidad que se supone que debo obtener con la inyección de dependencia"? DI no se trata de legibilidad. Se trata de desacoplar la dependencia entre objetos.
Parece que tienes Clase A creando Clase B, Clase B creando Clase C y Clase C creando Clase D.
Lo que debe tener es la clase B inyectada en la clase A. La clase C inyectada en la clase B. La clase D inyectada en la clase C.
fuente
No estoy seguro de por qué no quieres convertir tu fábrica de tiempo en un singleton. Si solo hay una instancia de ella en toda su aplicación, es de hecho un singleton.
Dicho esto, es muy peligroso compartir un objeto mutable, excepto si está debidamente protegido por bloques de sincronización, en cuyo caso, no hay razón para que no sea un singleton.
Si desea realizar una inyección de dependencia, es posible que desee ver la primavera u otros marcos de inyección de dependencia, lo que le permitiría asignar parámetros automáticamente mediante una anotación
fuente
Su objetivo es ser una respuesta complementaria a Doc Brown, y también responder a los comentarios no respondidos de Dinaiz que todavía están relacionados con la Pregunta.
Lo que probablemente necesite es un marco para hacer DI. Tener jerarquías complejas no significa necesariamente un mal diseño, pero si tiene que inyectar un TimeFactory de abajo hacia arriba (de A a D) en lugar de inyectar directamente a D, entonces probablemente haya algo mal con la forma en que está haciendo la Inyección de dependencia.
Un singleton? No, gracias. Si solo necesita una isancia, haga que se comparta en el contexto de su aplicación (el uso de un contenedor de IoC para DI como Infector ++ solo requiere vincular TimeFactory como una sola istance), este es el ejemplo (C ++ 11 por cierto, pero entonces C ++. Tal vez mover a C ++ 11 ya? Obtiene la aplicación sin fugas de forma gratuita):
Ahora, lo bueno de un contenedor de IoC es que no necesita pasar la fábrica de tiempo a D. Si su clase "D" necesita fábrica de tiempo, simplemente coloque fábrica de tiempo como parámetro de construcción para la clase D.
como ves, inyectas TimeFactory solo una vez. ¿Cómo usar "A"? Muy simple, cada clase se inyecta, se construye en general o se certifica con una fábrica.
cada vez que cree la clase A, se inyectará automáticamente (dependencia perezosa) todas las dependencias hasta D y D se inyectará con TimeFactory, por lo que al llamar solo 1 método tendrá lista su jerarquía completa (e incluso las jerarquías complejas se resuelven de esta manera eliminar MUCHO código de placa de caldera): no tiene que llamar "nuevo / eliminar" y eso es muy importante porque puede separar la lógica de la aplicación del código de pegamento.
Eso es fácil, su TimeFactory tiene un método de "creación", luego use una firma diferente "crear (parámetros)" y listo. Los parámetros que no son dependencias a menudo se resuelven de esta manera. Esto también elimina el deber de inyectar cosas como "cadenas" o "enteros" porque eso solo agrega placa de caldera adicional.
¿Quién crea a quién? El contenedor de IoC crea istances y fábricas, las fábricas crean el resto (las fábricas pueden crear diferentes objetos con parámetros arbitrarios, por lo que realmente no necesita un estado para las fábricas). Todavía puede usar las fábricas como envoltorios para el Contenedor de IoC: en general, Inyectar en el Contenedor de IoC es muy malo y es lo mismo que usar un localizador de servicios. Algunas personas resolvieron el problema envolviendo el contenedor de IoC con una fábrica (esto no es estrictamente necesario, pero tiene la ventaja de que el contenedor resuelve la jerarquía y que todas sus fábricas se vuelven aún más fáciles de mantener).
Tampoco abuses de la inyección de dependencia, los tipos simples pueden ser miembros de la clase o variables locales. Esto parece obvio, pero vi personas inyectando "std :: vector" solo porque había un marco DI que lo permitía. Recuerde siempre la ley de Demeter: "Inyecte solo lo que realmente necesita inyectar"
fuente
¿Tus clases A, B y C también necesitan crear instancias de tiempo o solo clase D? Si es solo clase D, entonces A y B no deberían saber nada sobre TimeFactory. Cree una instancia de TimeFactory dentro de la clase C y páselo a la clase D. Tenga en cuenta que "crear una instancia" no necesariamente quiero decir que la clase C tenga que ser responsable de crear instancias de TimeFactory. Puede recibir DClassFactory de la clase B, y DClassFactory sabe cómo crear una instancia de Time.
Una técnica que también uso a menudo cuando no tengo ningún marco DI es proporcionar dos constructores, uno que acepte una fábrica y otro que cree una fábrica predeterminada. El segundo generalmente tiene un acceso protegido / paquete, y se utiliza principalmente para pruebas unitarias.
fuente
Implementé otro marco de inyección de dependencia de C ++, que recientemente se propuso para impulsar - https://github.com/krzysztof-jusiak/di - la biblioteca es menos macro (gratis), solo encabezado, C ++ 03 / C ++ 11 / C ++ 14 biblioteca que proporciona tipo seguro, tiempo de compilación, inyección de dependencia del constructor libre de macros.
fuente