tipos patron inyeccion injection explicacion desarrollo dependency dependencias dependencia blog arquitectura design-patterns dependency-injection

design-patterns - patron - spring dependencias



¿Cuáles son las desventajas de usar la inyección de dependencia? (19)

Aquí está mi propia reacción inicial: Básicamente, las mismas desventajas de cualquier patrón.

  • se necesita tiempo para aprender
  • Si se malinterpreta, puede causar más daño que bien.
  • Si se lleva al extremo, puede ser más trabajo del que justificaría el beneficio.

Estoy tratando de presentar la DI como un patrón aquí en el trabajo y uno de nuestros desarrolladores principales desea saber: ¿Cuáles son los inconvenientes , si es que hay alguno, de usar el patrón de inyección de dependencia?

Tenga en cuenta que estoy buscando aquí 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 (consulte este artículo de Martin Fowler), no de un marco específico, ya sea basado en XML (como Spring) o basado en código (como Guice), o "auto-roll" .

Edit : Algunos grandes debates / ranting / debate en curso /r/programming aquí.


Código de legibilidad. No podrá averiguar fácilmente el flujo de código ya que las dependencias están ocultas en archivos XML.


DI es una técnica o un patrón y no está relacionado con ningún marco. Puedes cablear tus dependencias manualmente. DI lo ayuda con SR (responsabilidad única) y SoC (separación de inquietudes). DI lleva a un mejor diseño. Desde mi punto de vista y experiencia no hay desventajas . Al igual que con cualquier otro patrón, puede equivocarse o utilizarlo de forma incorrecta (pero, en el caso de la DI, es bastante difícil).

Si introduce DI como principio en una aplicación heredada, use un marco: el mayor error que puede hacer es usarlo incorrectamente como un Localizador de Servicios. ¡El marco DI + en sí mismo es genial y solo mejoró las cosas en todas partes que lo vi! Desde el punto de vista de la organización, existen problemas comunes con cada nuevo proceso, técnica, patrón, ...:

  • Tienes que entrenar tu equipo
  • Tienes que cambiar tu aplicación (que incluye riesgos)

En general, tienes que invertir tiempo y dinero , además de eso, ¡no hay inconvenientes, de verdad!


Dos cosas:

  • Requieren un soporte adicional de herramientas para verificar que la configuración sea válida.

Por ejemplo, IntelliJ (edición comercial) tiene soporte para verificar la validez de una configuración de Spring, y marcará errores como las 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 que el patrón de "torta" (como lo conoce la comunidad Scala) es una buena idea: el comprobador de tipos puede verificar el cableado entre los componentes. No tienes ese beneficio con anotaciones o XML.

  • Esto hace que el análisis estático global de programas sea muy difícil.

Los marcos como Spring o Guice dificultan la determinación estática de cómo se verá el gráfico de objetos 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.


El código sin ningún DI corre el riesgo bien conocido de enredarse con el código Spaghetti ; algunos síntomas son que las clases y los métodos son demasiado grandes, hacen demasiado y no se pueden cambiar, desglosar, reformular o probar fácilmente.

El código con DI utilizado mucho puede ser un 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 , que es bueno. Pero mirar las clases por su cuenta es difícil ver lo que hace el sistema en su conjunto, ya que esto depende de cómo encajan todas estas pequeñas partes, lo que es difícil de ver. Simplemente parece una gran pila de cosas pequeñas.

Al evitar la complejidad de los espaguetis de grandes bits de código acoplado dentro de una clase grande, corre el riesgo de otro tipo de complejidad, donde hay muchas clases pequeñas y 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 de ravioli con clases pequeñas que hacen solo una cosa es probablemente bueno. Incluso en exceso, no creo que sea tan malo como el código de espagueti. Pero ser consciente de que puede llevarse demasiado lejos es el primer paso para evitarlo. Siga los enlaces para discusión de cómo evitarlo.


El mayor "inconveniente" de la Inversión de control (no del todo DI, pero lo suficientemente cerca) es que tiende a eliminarse al tener un solo punto para ver una descripción general de un algoritmo. Sin embargo, eso es básicamente lo que sucede cuando se ha desacoplado el código: la capacidad de buscar en un solo lugar es un artefacto de acoplamiento apretado.


El mismo problema básico que se obtiene a menudo con la programación orientada a objetos, las reglas de estilo y casi todo lo demás. Es posible, de hecho, muy común, hacer demasiada abstracción y agregar demasiada indirección y, en general, aplicar buenas técnicas de manera excesiva y en lugares equivocados.

Cada patrón u otra construcción que apliques trae complejidad. La abstracción y el direccionamiento indirecto dispersan la información, a veces eliminando detalles irrelevantes, pero también a veces hacen más difícil comprender exactamente lo que está sucediendo. Cada regla que aplique trae inflexibilidad, descartando opciones que podrían ser el mejor enfoque.

El punto es escribir el código que hace el trabajo y es 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íe) es un parámetro. El código dependiente depende de los datos, y esos datos se inyectan por medio de pasar el parámetro.

Sí, es una tontería y no aborda el punto orientado a objetos de la inyección de dependencia, 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 problemas potenciales.

Tomemos esta función tradicional simple - la sintaxis de C ++ no es significativa aquí, pero tengo que deletrearla de alguna manera ...

void Say_Hello_World () { std::cout << "Hello World" << std::endl; }

Tengo una dependencia que quiero extraer e inyectar: ​​el texto "Hola mundo". Suficientemente fácil...

void Say_Something (const char *p_text) { std::cout << p_text << std::endl; }

¿Cómo es eso más inflexible que el original? Bueno, ¿y si decido que la salida debería ser Unicode? Probablemente quiero cambiar de std :: cout a std :: wcout. Pero eso significa que mis cadenas deben ser de wchar_t, no de char. O cada persona que llama tiene que cambiarse, o (más razonablemente), la implementación anterior se reemplaza con un adaptador que traduce la cadena y llama a la nueva implementación.

Eso es trabajo de mantenimiento allí mismo 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 de la API de Win32 ...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

Eso es 12 "dependencias" para tratar. Por ejemplo, si las resoluciones de pantalla son realmente enormes, tal vez necesitemos valores de coordenadas de 64 bits y otra versión de CreateWindowEx. Y sí, ya existe una versión más antigua, que presumiblemente se asigna a la versión más reciente entre bastidores ...

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 qué 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 sensibles" 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 el texto del código fuente como en el tiempo del desarrollador, y si esa clase se escribe para proporcionar dependencias de acuerdo con las especificaciones de algunos objetos dependientes, entonces el objeto dependiente está obligado a admitir esa interfaz, incluso si es necesario. Para reemplazar la implementación de ese objeto.

Nada de esto debe interpretarse como una afirmación de que la inyección de dependencia es mala, ni mucho menos. Pero cualquier buena técnica puede aplicarse excesivamente y en el lugar equivocado. Al igual que no es necesario extraer todas las cadenas y convertirlas en un parámetro, no es necesario extraer todos los comportamientos de bajo nivel de los objetos de alto nivel y convertirlos en una dependencia inyectable.


Esto es más de un nitpick. Pero una de las desventajas de la inyección de dependencia es que dificulta un poco más que las herramientas de desarrollo razonen y naveguen el código.

Específicamente, si presiona Control-Click / Command-Click en una invocación de método en el código, lo llevará a la declaración del método en una interfaz en lugar de la implementación concreta.

Esto es realmente un inconveniente más del código acoplado libremente (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 acoplado a las masas, así que pensé que lo mencionaría.

Además, los beneficios de un código débilmente acoplado son muy superiores a esto, por lo que lo llamo un nitpick. Aunque he trabajado el tiempo suficiente para saber que este es el tipo de rechazo que puede obtener si intenta introducir la inyección de dependencia.

De hecho, me atrevería a suponer que por cada "inconveniente" que puede encontrar para la inyección de dependencia, encontrará muchas ventajas que lo superan con creces.


He estado usando Guice (Java DI framework) ampliamente durante los últimos 6 meses. Si bien en general creo que es genial (especialmente desde una perspectiva de prueba), hay ciertos inconvenientes. Más destacado:

  • El código puede llegar a ser más difícil de entender. La inyección de dependencia se puede utilizar de maneras muy ... creativas ... Por ejemplo, acabo de encontrar un código que usaba una anotación personalizada para inyectar un cierto IOStreams (por ejemplo: @ Server1Stream, @ Server2Stream). Si bien esto funciona, y debo admitir que tiene cierta elegancia, hace que la comprensión de las inyecciones de Guice sea un requisito previo para entender el código.
  • Curva de aprendizaje superior al aprender proyecto. Esto se relaciona con el punto 1. Para entender cómo funciona un proyecto que utiliza la inyección de dependencia, debe comprender tanto el patrón de inyección de dependencia como el marco específico. Cuando empecé en mi trabajo actual, pasé unas cuantas horas confusas tratando de asimilar lo que Guice estaba haciendo entre bastidores.
  • Los constructores se hacen grandes. Aunque esto puede resolverse en gran medida con un constructor predeterminado o una fábrica.
  • Los errores pueden ser ofuscados. Mi ejemplo más reciente de esto fue que tuve una colisión con 2 nombres de bandera. Guice se tragó el error en silencio y una de mis banderas no fue inicializada.
  • Los errores son empujados al tiempo de ejecución. Si configura su módulo Guice incorrectamente (referencia circular, enlace incorrecto, ...) la mayoría de los errores no se descubren durante el tiempo de compilación. En su lugar, los errores se exponen cuando el programa se ejecuta realmente.

Ahora que me he quejado. Permítanme decirles que continuaré (de buena gana) utilizando Guice en mi proyecto actual y probablemente en mi próximo. La inyección de dependencia es un patrón excelente e increíblemente poderoso. Pero definitivamente puede ser confuso y es casi seguro que pasará un tiempo maldiciendo 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 usada en exceso.


La ilusión de que usted ha desacoplado su código simplemente implementando la inyección de dependencia sin REALMENTE desacoplarlo. Creo que eso es lo más peligroso de DI.


Me parece que la inyección del constructor puede llevar a constructores grandes y feos (y lo uso a lo largo de mi código base, ¿quizás 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, prefiero la inyección de construtor sobre la inyección de colocador porque una vez que mi objeto está construido, entonces sé sin duda en qué estado se encuentra, ya sea en un entorno de prueba de unidad o cargado en algún contenedor IOC. Lo cual, de una manera indirecta, es decir lo que creo que es el principal inconveniente de la inyección de setter.

(Como una nota al margen, me parece que el tema es bastante "religioso", ¡pero su millaje variará con el nivel de fanatismo técnico en su equipo de desarrollo!)



Parece que los supuestos beneficios de un lenguaje de tipo estático disminuyen significativamente cuando se emplean constantemente técnicas para evitar la escritura estática. Una gran tienda de Java en la que acabo de entrevistarme estaba haciendo un mapeo de sus dependencias de compilación con un análisis de código estático ... que tenía que analizar todos los archivos de Spring para ser efectivo.


Si está utilizando DI sin un contenedor IOC, el mayor inconveniente es que puede ver rápidamente cuántas dependencias tiene realmente su código y qué tan unido 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). El último inconveniente es que algunos desarrolladores comenzarán a escribir pruebas unitarias honestas a la bondad y les llevará tiempo resolverlo. Los desarrolladores que previamente pudieron arrancar algo en medio día de repente pasarán dos días tratando de averiguar cómo burlarse de todas sus dependencias.

Al igual que la respuesta de Mark Seemann, la conclusión es que pasa tiempo convirtiéndose en un mejor desarrollador en lugar de hackear fragmentos de código y lanzarlos a la producción. ¿Cuál preferiría su negocio? Sólo tú puedes responder eso.


Si tiene una solución propia, las dependencias están en su cara 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 gestionadas por el marco, si se llevan a los extremos, pueden comenzar a aparecer como magia.

Sin embargo, tener demasiadas dependencias en demasiadas clases es una clara señal de que la estructura de la clase está equivocada. De modo que, de alguna manera, la inyección de dependencia (desarrollada en casa o administrada por el marco) puede ayudar a sacar a la luz los problemas de diseño que de otra forma podrían ocultarse en la oscuridad.

Para ilustrar mejor el segundo punto, aquí hay un extracto de este article ( fuente original ) que creo sinceramente que es el problema fundamental en la construcción de cualquier sistema, no solo de los sistemas informáticos.

Supongamos que quieres diseñar un campus universitario. Debe delegar parte del diseño a los estudiantes y profesores, de lo contrario, el edificio de Física no funcionará bien para las personas de física. Ningún arquitecto sabe lo suficiente sobre lo que la gente de física necesita para hacer todo ellos mismos. Pero no puede delegar el diseño de cada habitación a sus ocupantes, porque entonces obtendrá una pila gigante de escombros.

¿Cómo puede distribuir la responsabilidad del diseño en todos los niveles de una gran jerarquía, mientras mantiene la coherencia y la armonía del diseño general? Este es el problema de diseño arquitectónico que Alexander está tratando de resolver, pero también es un problema fundamental del desarrollo de los sistemas informáticos.

¿DI resuelve este problema? No Pero le ayuda a ver claramente si está tratando de delegar la responsabilidad de diseñar cada habitación a sus ocupantes.


Un par de puntos:

  • DI aumenta la complejidad, generalmente al aumentar el número de clases ya que las responsabilidades se separan más, lo que no siempre es beneficioso
  • Su código estará (de alguna manera) acoplado al marco de inyección de dependencias que usa (o, más generalmente, cómo decide implementar el patrón DI)
  • Los contenedores o enfoques DI que realizan la resolución de tipos generalmente incurren en una leve penalización en tiempo de ejecución (muy despreciable, pero está ahí)

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.


Una cosa que me hace retorcerme un poco con DI es la suposición de que todos los objetos inyectados son baratos para crear instancias y no producen efectos secundarios -O- la dependencia se usa con tanta frecuencia que supera cualquier costo asociado de creación de instancias.

Donde esto puede ser significativo es cuando una dependencia no se usa con frecuencia dentro de una clase consumidora; como algo como un IExceptionLogHandlerService . Obviamente, un servicio como este se invoca (con suerte :)) rara vez dentro de la clase, presumiblemente solo en las excepciones que necesitan ser registradas; sin embargo, el constructor canónico de inyección patrón ...

Public Class MyClass Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService Public Sub New(exLogHandlerService As IExceptionLogHandlerService) Me.mExLogHandlerService = exLogHandlerService End Sub ... End Class

... requiere que se proporcione una instancia "en vivo" de este servicio, maldita sea el costo / los efectos secundarios necesarios para llevarlo allí. No es probable que lo hiciera, pero ¿qué pasaría si la creación de esta instancia de dependencia involucrara un servicio / base de datos, o búsquedas de archivos de configuración, o bloqueara un recurso hasta que se eliminara? Si este servicio se construyera en su lugar según sea necesario, se encuentre en el servicio o se genere en la fábrica (todos tienen problemas propios), entonces usted tomaría el costo de la 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 si bien esa es una buena idea, no siempre es así. El uso típico de inyección de constructor, sin embargo, básicamente exige que este sea el caso. Es decir, cuando crea una implementación de una dependencia, debe diseñarla teniendo en cuenta DI. Tal vez hubiera hecho que la construcción de objetos fuera más costosa para obtener beneficios en otros lugares, pero si esta implementación se va a inyectar, es probable que lo obligue a reconsiderar ese diseño.

Por cierto, ciertas técnicas pueden mitigar este problema exacto permitiendo la carga perezosa de dependencias inyectadas, por ejemplo, proporcionando una clase de una instancia Lazy<IService> como la dependencia. Esto cambiaría los constructores de los objetos dependientes y, por lo tanto, sería aún más consciente de los detalles de la implementación, como el gasto en la construcción del objeto, lo que posiblemente tampoco sea deseable.


La inyección de dependencia basada en constructores (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, a lo largo de los años que pasé con otros ex colegas de Martin Fowler, comencé a notar que la mayoría de las clases buenas escritas de esta manera terminan teniendo un solo método de hacer algo.

El principal inconveniente, entonces, es que una vez que te das cuenta de que solo se trata de una forma sencilla de OO para escribir cierres como clases para obtener los beneficios de la programación funcional, tu motivación para escribir código OO puede evaporarse rápidamente.


Puede aumentar el tiempo de inicio de la aplicación porque el contenedor IoC debe resolver las dependencias de una manera adecuada y, a veces, requiere realizar varias iteraciones.