Estoy tratando de introducir la DI como un patrón aquí en el trabajo y uno de nuestros desarrolladores principales quisiera saber: ¿Cuáles son las desventajas de usar el patrón de Inyección de dependencias?
Tenga en cuenta que estoy buscando una lista exhaustiva, si es posible, no una discusión subjetiva sobre el tema.
Aclaración : estoy hablando del patrón de inyección de dependencia (ver este artículo de Martin Fowler), no un marco específico, ya sea basado en XML (como Spring) o basado en código (como Guice), o "auto-enrollado" .
Editar : alguna gran discusión / despotricar / debate en curso / r / programación aquí.
Respuestas:
Un par de puntos:
En general, el beneficio del desacoplamiento hace que cada tarea sea más fácil de leer y comprender, pero aumenta la complejidad de organizar las tareas más complejas.
fuente
El mismo problema básico que a menudo se presenta con la programación orientada a objetos, las reglas de estilo y casi todo lo demás. Es posible, muy común, de hecho, hacer demasiada abstracción y agregar demasiada indirección, y en general aplicar buenas técnicas en exceso y en los lugares equivocados.
Cada patrón u otra construcción que aplique trae complejidad. La abstracción y la indirección dispersan la información, a veces eliminando detalles irrelevantes, pero igualmente a veces dificultan la comprensión exacta de lo que está sucediendo. Cada regla que aplica trae inflexibilidad, descartando opciones que podrían ser el mejor enfoque.
El punto es escribir código que haga el trabajo y sea robusto, legible y mantenible. Usted es un desarrollador de software, no un constructor de torres de marfil.
Enlaces Relevantes
http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx
http://www.joelonsoftware.com/articles/fog0000000018.html
Probablemente la forma más simple de inyección de dependencia (no se ría) es un parámetro. El código dependiente depende de los datos, y esos datos se inyectan al pasar el parámetro.
Sí, es una tontería y no aborda el punto de inyección de dependencia orientado a objetos, pero un programador funcional le dirá que (si tiene funciones de primera clase) este es el único tipo de inyección de dependencia que necesita. El punto aquí es tomar un ejemplo trivial y mostrar los posibles problemas.
Tomemos esta función tradicional simple: la sintaxis de C ++ no es significativa aquí, pero tengo que deletrearla de alguna manera ...
Tengo una dependencia que quiero extraer e inyectar: el texto "Hola mundo". Suficientemente fácil...
¿Cómo es eso más inflexible que el original? Bueno, ¿y si decido que la salida debe ser unicode? Probablemente quiera cambiar de std :: cout a std :: wcout. Pero eso significa que mis cadenas deben ser de wchar_t, no de char. O bien cada persona que llama tiene que ser cambiada, o (más razonablemente), la implementación anterior se reemplaza con un adaptador que traduce la cadena y llama a la nueva implementación.
Ese es el trabajo de mantenimiento que no sería necesario si hubiéramos conservado el original.
Y si parece trivial, eche un vistazo a esta función del mundo real desde la API Win32 ...
http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx
Son 12 "dependencias" con las que lidiar. Por ejemplo, si las resoluciones de pantalla se vuelven realmente enormes, tal vez necesitemos valores de coordenadas de 64 bits, y otra versión de CreateWindowEx. Y sí, ya hay una versión anterior todavía pendiente, que presumiblemente se asigna a la versión más nueva detrás de escena ...
http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx
Esas "dependencias" no son solo un problema para el desarrollador original: todos los que usan esa interfaz tienen que buscar cuáles son las dependencias, cómo se especifican y qué significan, y determinar qué hacer para su aplicación. Aquí es donde las palabras "valores predeterminados razonables" pueden hacer la vida mucho más simple.
La inyección de dependencia orientada a objetos no es diferente en principio. Escribir una clase es una sobrecarga, tanto en texto de código fuente como en tiempo de desarrollador, y si esa clase se escribe para suministrar dependencias de acuerdo con algunas especificaciones de objetos dependientes, entonces el objeto dependiente está bloqueado para admitir esa interfaz, incluso si es necesario para reemplazar la implementación de ese objeto.
Nada de esto debe leerse como afirmando que la inyección de dependencia es mala, ni mucho menos. Pero cualquier buena técnica se puede aplicar en exceso y en el lugar equivocado. Del mismo modo que no todas las cadenas deben extraerse y convertirse en un parámetro, no todos los comportamientos de bajo nivel deben extraerse de los objetos de alto nivel y convertirse en una dependencia inyectable.
fuente
Aquí está mi propia reacción inicial: básicamente las mismas desventajas de cualquier patrón.
fuente
El mayor "inconveniente" de la Inversión de control (no exactamente DI, pero lo suficientemente cerca) es que tiende a eliminar tener un solo punto para ver una descripción general de un algoritmo. Sin embargo, eso es básicamente lo que sucede cuando se desacopla el código: la capacidad de mirar en un lugar es un artefacto de acoplamiento estrecho.
fuente
No creo que tal lista exista, sin embargo, intente leer esos artículos:
DI puede ocultar el código (si no está trabajando con un buen IDE)
El mal uso de IoC puede conducir a un código incorrecto según el tío Bob.
Es necesario estar atento a la sobre ingeniería y crear versatilidad innecesaria.
fuente
He estado usando Guice (marco Java DI) ampliamente durante los últimos 6 meses. Si bien en general creo que es genial (especialmente desde una perspectiva de prueba), hay ciertas desventajas. Más destacado:
Ahora que me he quejado. Permítanme decir que continuaré (voluntariamente) usando Guice en mi proyecto actual y muy probablemente en el próximo. La inyección de dependencia es un patrón excelente e increíblemente poderoso. Pero definitivamente puede ser confuso y seguramente pasará algún tiempo maldiciendo en el marco de inyección de dependencia que elija.
Además, estoy de acuerdo con otros carteles en que la inyección de dependencia puede ser utilizada en exceso.
fuente
El código sin DI corre el riesgo bien conocido de enredarse en el código Spaghetti : algunos síntomas son que las clases y los métodos son demasiado grandes, hacen demasiado y no pueden cambiarse, descomponerse, refaccionarse o probarse fácilmente.
El código con DI que se usa mucho puede ser código de Ravioli donde cada clase pequeña es como una pepita de ravioli individual: hace una pequeña cosa y se cumple el principio de responsabilidad única , lo cual es bueno. Pero mirar las clases por su cuenta es difícil ver qué hace el sistema en su conjunto, ya que esto depende de cómo encajan todas estas partes pequeñas, lo cual es difícil de ver. Simplemente parece una gran pila de cosas pequeñas.
Al evitar la complejidad de los espaguetis de grandes fragmentos de código acoplado dentro de una clase grande, corre el riesgo de otro tipo de complejidad, donde hay muchas pequeñas clases simples y las interacciones entre ellas son complejas.
No creo que este sea un inconveniente fatal: la DI todavía vale mucho la pena. Algún grado de estilo ravioli con clases pequeñas que hacen una sola cosa es probablemente bueno. Incluso en exceso, no creo que sea tan malo como el código de espagueti. Pero ser consciente de que se puede llevar demasiado lejos es el primer paso para evitarlo. Siga los enlaces para discutir cómo evitarlo.
fuente
Si tiene una solución local, las dependencias están directamente en el constructor. O tal vez como parámetros del método, que de nuevo no es demasiado difícil de detectar. Aunque las dependencias administradas por el marco, si se llevan a los extremos, pueden comenzar a parecer mágicas.
Sin embargo, tener demasiadas dependencias en demasiadas clases es una señal clara de que la estructura de su clase está jodida. Por lo tanto, de alguna manera, la inyección de dependencia (local o administrada por el marco) puede ayudar a resaltar problemas de diseño evidentes que de otro modo podrían estar ocultos al acecho en la oscuridad.
Para ilustrar mejor el segundo punto, aquí hay un extracto de este artículo ( fuente original ) que creo sinceramente que es el problema fundamental en la construcción de cualquier sistema, no solo sistemas informáticos.
¿DI resuelve este problema? No se . Pero sí le ayuda a ver claramente si está tratando de delegar la responsabilidad de diseñar cada habitación a sus ocupantes.
fuente
Una cosa que me hace retorcer un poco con DI es la suposición de que todos los objetos inyectados son baratos de instanciar y no producen efectos secundarios, O la dependencia se usa con tanta frecuencia que supera cualquier costo de instanciación asociado.
Donde esto puede ser significativo es cuando una dependencia no se usa con frecuencia dentro de una clase consumidora; como algo así como un
IExceptionLogHandlerService
. Obviamente, un servicio como este se invoca (con suerte :)) rara vez dentro de la clase, presumiblemente solo en excepciones que deben registrarse; sin embargo, el patrón de inyección de constructor canónico ...... requiere que se proporcione una instancia "en vivo" de este servicio, condenados los costos / efectos secundarios necesarios para llegar allí. No es probable que lo haga, pero ¿qué pasaría si la construcción de esta instancia de dependencia involucrara un hit de servicio / base de datos, o búsquedas de archivos de configuración, o bloqueara un recurso hasta que se elimine? Si este servicio se construyó según sea necesario, ubicado en el servicio o generado en la fábrica (todos tienen problemas propios), entonces usted estaría tomando el costo de construcción solo cuando sea necesario.
Ahora, es un principio de diseño de software generalmente aceptado que construir un objeto es barato y no produce efectos secundarios. Y aunque esa es una buena idea, no siempre es el caso. El uso de la típica inyección de constructor, sin embargo, básicamente exige que sea así. Es decir, cuando crea una implementación de una dependencia, debe diseñarla teniendo en cuenta la DI. Tal vez habría hecho que la construcción de objetos sea más costosa para obtener beneficios en otros lugares, pero si se va a inyectar esta implementación, es probable que lo obligue a reconsiderar ese diseño.
Por cierto, ciertas técnicas pueden mitigar este problema exacto al permitir la carga diferida de las dependencias inyectadas, por ejemplo, proporcionando a una clase una
Lazy<IService>
instancia como dependencia. Esto cambiaría los constructores de sus objetos dependientes y haría aún más conscientes de los detalles de implementación, como los gastos de construcción de objetos, lo que posiblemente tampoco sea deseable.fuente
this.errorLogger.WriteError(ex)
cuando se produce un error en una declaración try / catch.La ilusión de que ha desacoplado su código con solo implementar la inyección de dependencia sin REALMENTE desacoplarlo. Creo que eso es lo más peligroso de DI.
fuente
Esto es más de un punto crítico. Pero una de las desventajas de la inyección de dependencia es que dificulta un poco el razonamiento y la navegación por el código de las herramientas de desarrollo.
Específicamente, si Control-Click / Command-Click en una invocación de método en código, lo llevará a la declaración del método en una interfaz en lugar de la implementación concreta.
Esto es realmente más una desventaja del código débilmente acoplado (código diseñado por la interfaz), y se aplica incluso si no usa la inyección de dependencia (es decir, incluso si simplemente usa fábricas). Pero el advenimiento de la inyección de dependencia es lo que realmente alentó el código débilmente acoplado a las masas, así que pensé en mencionarlo.
Además, los beneficios del código débilmente acoplado superan con creces esto, por lo que lo llamo una trampa. Aunque he trabajado lo suficiente como para saber que este es el tipo de retroceso que puede obtener si intenta introducir la inyección de dependencia.
De hecho, me aventuraría a adivinar que por cada "inconveniente" que pueda encontrar para la inyección de dependencia, encontrará muchas ventajas que lo superan con creces.
fuente
La inyección de dependencia basada en el constructor (sin la ayuda de "marcos" mágicos) es una forma limpia y beneficiosa de estructurar el código OO. En las mejores bases de código que he visto, durante los años que pasé con otros ex colegas de Martin Fowler, comencé a notar que la mayoría de las buenas clases escritas de esta manera terminan teniendo un único
doSomething
método.El principal inconveniente, entonces, es que una vez que te das cuenta de que todo es solo una forma de escribir OO de clase larga y grosera como clases para obtener los beneficios de la programación funcional, tu motivación para escribir código OO puede evaporarse rápidamente.
fuente
Encuentro que la inyección de constructor puede conducir a grandes constructores feos (y lo uso en toda mi base de código, ¿tal vez mis objetos son demasiado granulares?). Además, a veces con la inyección del constructor termino con horribles dependencias circulares (aunque esto es muy raro), por lo que es posible que tenga que tener algún tipo de ciclo de vida de estado listo con varias rondas de inyección de dependencia en un sistema más complejo.
Sin embargo, estoy a favor de la inyección de construtor sobre la inyección de setter porque una vez que mi objeto está construido, entonces sé sin duda en qué estado se encuentra, si está en un entorno de prueba unitaria o cargado en algún contenedor de COI. Lo que, de una manera indirecta, dice que lo que siento es el principal inconveniente con la inyección de setter.
(Como nota al margen, encuentro todo el tema bastante "religioso", ¡pero su kilometraje variará con el nivel de fanatismo técnico en su equipo de desarrollo!)
fuente
Si está utilizando DI sin un contenedor IOC, el mayor inconveniente es que rápidamente ve cuántas dependencias tiene realmente su código y qué tan estrechamente acoplado está todo. ("¡Pero pensé que era un buen diseño!") La progresión natural es avanzar hacia un contenedor de COI que puede tomar un poco de tiempo para aprender e implementar (no es tan malo como la curva de aprendizaje de WPF, pero no es gratis ya sea). La desventaja final es que algunos desarrolladores comenzarán a escribir honestas pruebas de unidad de bondad y les tomará tiempo darse cuenta. Los desarrolladores que previamente pudieron hacer algo en medio día de repente pasarán dos días tratando de descubrir cómo burlarse de todas sus dependencias.
De manera similar a la respuesta de Mark Seemann, la conclusión es que pasas tiempo convirtiéndote en un mejor desarrollador en lugar de hackear fragmentos de código y lanzarlos a la producción. ¿Cuál preferiría tener su negocio? Solo tú puedes responder eso.
fuente
DI es una técnica o un patrón y no está relacionado con ningún marco. Puede conectar sus dependencias manualmente. DI le ayuda con SR (responsabilidad única) y SoC (separación de preocupaciones). DI conduce a un mejor diseño. Desde mi punto de vista y experiencia no hay inconvenientes . Al igual que con cualquier otro patrón, puede equivocarse o usarlo incorrectamente (pero lo que es bastante difícil en el caso de DI).
Si introduce DI como principio a una aplicación heredada, utilizando un marco, el error más grande que puede hacer es usarlo incorrectamente como un Localizador de servicios. DI + Framework en sí es genial y solo mejoró las cosas en todos los lugares donde lo vi. Desde el punto de vista organizacional, existen los problemas comunes con cada nuevo proceso, técnica, patrón, ...:
En general, tienes que invertir tiempo y dinero , además de eso, ¡realmente no hay inconvenientes!
fuente
Legibilidad de código. No podrá averiguar fácilmente el flujo de código ya que las dependencias están ocultas en los archivos XML.
fuente
Dos cosas:
Por ejemplo, IntelliJ (edición comercial) tiene soporte para verificar la validez de una configuración de Spring y marcará errores como violaciones de tipo en la configuración. Sin ese tipo de soporte de herramientas, no puede verificar si la configuración es válida antes de ejecutar pruebas.
Esta es una de las razones por las cuales el patrón de 'pastel' (como es conocido por la comunidad Scala) es una buena idea: el cableado entre componentes puede ser verificado por el verificador de tipo. No tiene ese beneficio con anotaciones o XML.
Los marcos como Spring o Guice hacen que sea difícil determinar estáticamente cómo se verá el gráfico de objeto creado por el contenedor. Aunque crean un gráfico de objetos cuando se inicia el contenedor, no proporcionan API útiles que describan el gráfico de objetos que se crearía.
fuente
Parece que los supuestos beneficios de un lenguaje de tipo estático disminuyen significativamente cuando empleas constantemente técnicas para evitar el tipeo estático. Una gran tienda de Java en la que acabo de entrevistar estaba mapeando sus dependencias de compilación con análisis de código estático ... que tuvo que analizar todos los archivos de Spring para ser efectivo.
fuente
Puede aumentar el tiempo de inicio de la aplicación porque el contenedor IoC debería resolver las dependencias de manera adecuada y, a veces, requiere realizar varias iteraciones.
fuente