¿Cuáles son las desventajas de usar la inyección de dependencia? [cerrado]

336

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í.

Epaga
fuente
Puede ser una buena idea especificar si se supone que debatimos sobre DI o un tipo específico de herramienta que lo admite (basado en XML o no).
Jesse Millikan
55
la diferencia es que NO estoy buscando respuestas que solo se apliquen a marcos específicos, como "XML apesta". :) Estoy buscando respuestas que se apliquen al concepto de DI según lo descrito por Fowler aquí: martinfowler.com/articles/injection.html
Epaga
3
No veo inconvenientes para DI en general (constructor, setter o método). Se desacopla y simplifica sin gastos generales.
Kissaki
2
@kissaki bien aparte del setter inyectar cosas. Lo mejor es hacer objetos que se puedan usar en la construcción. Si no me cree, trate de añadir otro colaborador a un objeto con 'incubadora' de inyección, obtendrá una NPE con seguridad ....
time4tea

Respuestas:

209

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á (un tanto) acoplado al marco de inyección de dependencia que usa (o más generalmente, cómo decide implementar el patrón DI)
  • Los contenedores DI o enfoques que realizan resolución de tipos generalmente incurren en una leve penalización de tiempo de ejecución (muy insignificante, 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.

Håvard S
fuente
72
- La separación de clases reduce la complejidad. Muchas clases no hacen que una aplicación sea compleja. - Solo debe tener una dependencia en su marco DI en la raíz de la aplicación.
Robert
9191
Somos humanos; nuestra memoria a corto plazo es limitada y no puede manejar muchos <xxx> simultáneamente. Es lo mismo para las clases que para los métodos, funciones, archivos o cualquier construcción que use para desarrollar su programa.
Håvard S
45
@Havard S: Sí, pero 5 clases realmente complejas no son más simples que 15 simples. La forma de disminuir la complejidad es reducir el conjunto de trabajo requerido por el programador que trabaja en el código; las clases complejas y altamente interdependientes no logran eso.
kyoryu
35
@kyoryu Creo que todos estamos de acuerdo en eso. Tenga en cuenta que la complejidad no es lo mismo que el acoplamiento . La disminución del acoplamiento puede aumentar la complejidad. Realmente no estoy diciendo que la DI sea algo malo porque aumenta el número de clases, estoy destacando los posibles inconvenientes asociados con ella. :)
Håvard S
96
+1 para "DI aumenta la complejidad" Eso es cierto en DI y más allá. (Casi) cada vez que aumentamos la flexibilidad, vamos a aumentar la complejidad. Se trata de equilibrio, que comienza con conocer las ventajas y desventajas. Cuando la gente dice "no hay inconvenientes", es un indicador seguro de que aún no han entendido completamente la cosa.
Don Branson,
183

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 ...

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 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.

Steve314
fuente
3
La inyección de dependencia no tiene ninguno de esos inconvenientes. Solo cambia la forma en que pasa las dependencias a los objetos, lo que no agrega complejidad ni inflexibilidad. Todo lo contrario.
Kissaki
24
@Kissaki: sí, cambia la forma en que pasas tus dependencias. Y tienes que escribir código para manejar ese paso de dependencias. Y antes de hacerlo, debe determinar qué dependencias pasar, definirlas de una manera que tenga sentido para alguien que no codificó el código dependiente, etc. Puede evitar el impacto en el tiempo de ejecución (por ejemplo, usar parámetros de políticas para las plantillas en C ++), pero todavía es un código que debe escribir y mantener. Si está justificado, es un precio muy pequeño para una gran recompensa, pero si finges que no hay precio que pagar, eso solo significa que pagarás ese precio cuando no hay ganancia.
Steve314
66
@Kissaki: en cuanto a la inflexibilidad, una vez que ha especificado cuáles son sus dependencias, está encerrado en eso; otras personas tienen dependencias escritas para inyectar de acuerdo con esa especificación. Si necesita una nueva implementación para el código dependiente que necesita dependencias ligeramente diferentes, difícil, está bloqueado. Es hora de comenzar a escribir algunos adaptadores para esas dependencias (y un poco más de gastos generales y otra capa de abstracción).
Steve314
No estoy seguro de entender este ejemplo, si considera la sobrecarga. Para generar el literal "Hello World", usamos la firma (). Para generar una cadena de 8 bits, use la firma (char). Para generar unicode, la sobrecarga (wchar_t). Obviamente, en este caso, (wchar_t) es el que los demás llaman detrás de escena. No se necesita mucha reescritura de código allí. ¿Me estoy perdiendo de algo?
ingrediente_15939
2
@ ingrediente_15939 - sí, este es un ejemplo de juguete simplificado. Si realmente estuviera haciendo esto, simplemente usaría la sobrecarga en lugar de un adaptador, ya que el costo de duplicar el código es menor que el costo del adaptador. Pero tenga en cuenta que la duplicación de código generalmente es algo malo, y el uso de adaptadores para evitar la duplicación de código es otra buena técnica (que a veces se puede usar demasiado y en los lugares equivocados).
Steve314
77

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

  • toma tiempo aprender
  • Si no se entiende, puede causar más daño que bien
  • si se lleva al extremo, puede ser más trabajo del que justificaría el beneficio
Epaga
fuente
2
¿Por qué lleva tiempo aprender? Pensé que simplifica las cosas en comparación con ejb.
fastcodejava
8
Esto parece ondulado considerando que no quieres una discusión subjetiva. Sugiero construir (colectivamente, si es necesario) un ejemplo realista para el examen. Construir una muestra sin DI sería un buen punto de partida. Entonces podríamos desafiarlo a los cambios de requisitos y examinar el impacto. (Esto es extremadamente conveniente para mí sugerir, por cierto, ya que estoy a punto de irme a la cama.)
Jesse Millikan
44
Esto realmente necesita algunos ejemplos para respaldarlo, y habiendo enseñado a docenas de personas DI, puedo decirles que realmente es un concepto muy simple de aprender y comenzar a poner en práctica.
chillitom
44
Realmente no considero DI un patrón. (Junto con IoC) son realmente más un modelo de programación: si los sigue completamente, su código se parece mucho más a un actor que a un OO pseudoprocesal "típico".
kyoryu
1
+1 Estoy usando Symfony 2, el DI está en todo el código del usuario, se está agotando solo con usarlo.
Pop
45

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.

Kyoryu
fuente
2
Pero seguramente ese "inconveniente" se debe a la naturaleza del problema que estamos resolviendo, desacoplarlo para que podamos cambiar fácilmente la implementación significa que no hay un solo lugar para mirar, y ¿qué relevancia tiene poder mirarlo? El único caso que puedo pensar es en la depuración y el entorno de depuración debería poder entrar en la implementación.
vickirk
3
Esta es la razón por la cual la palabra "inconveniente" estaba entre comillas :) Un acoplamiento flojo y una fuerte encapsulación impiden "un lugar para ver todo", por definición. Vea mis comentarios sobre la respuesta de Havard S, si tiene la sensación de que estoy en contra de DI / IoC.
kyoryu
1
Estoy de acuerdo (incluso te voté). Solo estaba haciendo el punto.
vickirk
1
@vickirk: La desventaja, supongo, es que es difícil de comprender para un humano. Desacoplar las cosas aumenta la complejidad en el sentido de que es más difícil de entender para los humanos, y lleva más tiempo comprenderlas por completo.
Bjarke Freund-Hansen
2
Por el contrario, una ventaja de DI es que puede observar el constructor de una clase individual y determinar instantáneamente de qué clases depende (es decir, qué clases necesita para hacer su trabajo). Esto es mucho más difícil para otro código, ya que el código puede crear clases willy-nilly.
Contango
42

No creo que tal lista exista, sin embargo, intente leer esos artículos:

Gabriel Ščerbák
fuente
77
Tengo curiosidad, cuando alguien pregunta por wonsides, ¿por qué las respuestas en un espíritu "no hay" se votan y las que contienen alguna información relacionada con la pregunta no?
Gabriel Ščerbák
12
-1 porque no estoy buscando una colección de enlaces, estoy buscando respuestas reales: ¿qué tal resumir los puntos de cada artículo? Entonces votaría en su lugar. Tenga en cuenta que los artículos en sí son bastante interesantes, aunque parece que los dos primeros en realidad son desmentidos negativos de DI.
Epaga
44
Me pregunto si la respuesta más votada también recibió una votación negativa, porque realmente no responde a la pregunta en absoluto. Estoy seguro de que sé lo que preguntaste y lo que respondí, no estoy pidiendo votos a favor, solo estoy confundido por qué mi la respuesta no está ayudando, mientras que aparentemente la desventaja de DI es que es demasiado bueno está ayudando mucho.
Gabriel Ščerbák
41

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:

  • El código puede volverse más difícil de entender. La inyección de dependencia se puede usar de maneras muy ... creativas ... Por ejemplo, acabo de encontrar algún código que utiliza una anotación personalizada para inyectar un determinado IOStreams (por ejemplo: @ Server1Stream, @ Server2Stream). Si bien esto funciona, y admito que tiene cierta elegancia, hace que entender las inyecciones de Guice sea un requisito previo para comprender el código.
  • Mayor curva de aprendizaje al aprender proyecto. Esto está relacionado con el punto 1. Para comprender cómo funciona un proyecto que utiliza inyección de dependencia, debe comprender tanto el patrón de inyección de dependencia como el marco específico. Cuando comencé en mi trabajo actual, pasé algunas horas confusas asimilando lo que Guice estaba haciendo detrás de escena.
  • Los constructores se hacen grandes. Aunque esto se puede resolver 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 en 2 nombres de banderas. Guice se tragó el error en silencio y una de mis banderas no se inicializó.
  • Los errores se envían al tiempo de ejecución. Si configura incorrectamente su módulo Guice (referencia circular, enlace incorrecto, ...), la mayoría de los errores no se descubren durante el tiempo de compilación. En cambio, los errores se exponen cuando el programa se ejecuta realmente.

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.

Andy Peck
fuente
14
No entiendo por qué las personas no hacen una gran cantidad de "Los errores son empujados al tiempo de ejecución" para mí eso es un factor decisivo, los errores de tipeo estático y el tiempo de compilación son el mejor regalo dado a los desarrolladores, no los arrojaría ellos lejos por cualquier cosa
Richard Tingle
2
@ RichardTingle, IMO durante el inicio de la aplicación Los módulos DI se inicializarán primero, por lo que cualquier configuración incorrecta en el módulo será visible tan pronto como la aplicación se inicie, en lugar de transcurridos algunos días u horas. También es posible cargar módulos de forma incremental, pero si nos atenemos al espíritu de DI al limitar la carga del módulo antes de inicializar la lógica de la aplicación, podemos aislar con éxito los enlaces defectuosos al inicio de la aplicación. Pero si lo configuramos como un anti patrón de localizador de servicio, entonces esos enlaces malos seguramente serán una sorpresa.
k4vin
@ RichardTingle Entiendo que nunca será similar a la red de seguridad proporcionada por el compilador, pero DI como una herramienta utilizada correctamente como se indica en el cuadro, esos errores de tiempo de ejecución se limitan a la inicialización de la aplicación. Entonces podemos ver la inicialización de la aplicación como una especie de fase de compilación para los módulos DI. En mi experiencia, la mayoría de las veces, si la aplicación se inicia, no habrá enlaces malos ni referencias incorrectas. PD: he estado usando NInject con C #
k4vin
@ RichardTingle: estoy de acuerdo con usted, pero es una compensación para obtener un código libremente acoplable y, por lo tanto, comprobable. Y como dijo k4vin, las dependencias faltantes se encuentran en la inicialización, y el uso de interfaces aún ayudará con los errores de tiempo de compilación.
Andrei Epure
1
Yo agregaría "Código difícil de mantener limpio" en su lista. Nunca se sabe si puede eliminar un registro antes de probar exhaustivamente el código sin él.
fernacolo
24

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.

Anthony
fuente
1
Sí, me gusta ese término "código de ravioles"; Lo expreso de manera más interminable cuando hablo sobre la desventaja de la DI. Aún así, no puedo imaginar desarrollar ningún framework o aplicación real en Java sin DI.
Howard M. Lewis Barco
13

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.

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 hacerlo todo por sí misma. 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 a través de todos los niveles de una gran jerarquía, manteniendo 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 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.

Anurag
fuente
13

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 ...

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, 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.

ckittel
fuente
1
Entiendo lo que estás diciendo, pero no creo que sea justo decir "cuando creas una implementación de una dependencia, tienes que diseñarla con DI en mente". Creo que una declaración más precisa sería "DI funciona mejor cuando se usa con clases que se implementan con las mejores prácticas con respecto al costo de instanciación y la falta de efectos secundarios o modos de falla durante la construcción ". En el peor de los casos, siempre podría inyectar una implementación de proxy diferido que difiera la asignación del objeto real hasta el primer uso.
Jolly Roger, el
1
Cualquier contenedor moderno de IoC le permitirá especificar la vida útil de un objeto para un tipo / interfaz abstracto específico (siempre único, singleton, http, etc.). También puede proporcionar un método / delegado de fábrica para instanciarlo perezosamente usando Func <T> o Lazy <T>.
Dmitry S.
Correcto. Instanciar dependencias innecesariamente cuesta memoria. Normalmente utilizo "carga perezosa" con getters que solo crean instancias de una clase cuando se me solicita. Puede proporcionar un constructor predeterminado sin parámetros, así como constructores posteriores que acepten dependencias instanciadas. En el primer caso, una clase que implementa IErrorLogger, por ejemplo, solo se instancia this.errorLogger.WriteError(ex)cuando se produce un error en una declaración try / catch.
John Bonfardeci
12

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.

Joey Guerra
fuente
11

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.

Jack Leow
fuente
55
Ctrl-shift-B con resharper te lleva a la implementación
adrianm
1
Pero, de nuevo, no tendríamos (en un mundo ideal) al menos 2 implementaciones de todo, es decir, al menos una implementación real y una simulación para pruebas unitarias ;-)
vickirk
1
En Eclipse, si mantiene presionada la tecla Ctrl y pasa el cursor sobre el código de invocación del método, le mostrará un menú para abrir la Declaración o la Implementación.
Sam Hasler
Si está en la definición de la interfaz, resalte el nombre del método y presione Ctrl + T para que aparezca la jerarquía de tipos para ese método; verá cada implementador de ese método en un árbol de jerarquía de tipos.
Qualidafial
10

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 doSomethingmé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.

sanityinc
fuente
1
El problema con el constructor es que siempre necesitarás agregar más argumentos de constructor ...
Miguel Ping
1
Jaja. Si hace eso demasiado pronto verá que su código es malo y lo refactorizará. Además, ctrl-f6 no es tan difícil.
time4tea
Y, por supuesto, lo contrario también es cierto: todo es solo una forma funcional y grosera de escribir clases como cierres para obtener los beneficios de la programación OO
CurtainDog
Hace muchos años construí un sistema de objetos usando cierres en Perl, así que entiendo lo que estás diciendo, pero encapsular datos no es un beneficio único de la programación OO, por lo que no está claro cuáles son estas características beneficiosas de OO que serían comparables Kludgy y de mano larga para obtener en un lenguaje funcional.
sanityinc
9

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!)

James B
fuente
8
Si tienes grandes constructores feos, ¿pueden ser tus clases demasiado grandes y solo tienes muchas dependencias?
Robert
44
¡Ciertamente es una posibilidad que estoy dispuesto a entretener! ... Lamentablemente, no tengo un equipo con el que pueda hacer evaluaciones por pares. los violines tocan suavemente en el fondo, y luego la pantalla se desvanece a negro ...
James B
1
Como Mark Seeman triste arriba "Puede ser peligroso para tu carrera" ...
Robert
No tenía idea de que las narraciones de fondo podrían ser tan expresivas aquí: P
Anurag
@Robert Estoy tratando de encontrar la cita, ¿dónde dijo eso arriba?
liang
8

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.

Mike Post
fuente
Buen punto. Eso es mala calidad / entrega rápida versus buena calidad / entrega lenta. ¿La baja? DI no es mágico, esperar que lo sea es el inconveniente.
liang
5

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, ...:

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

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

Robert
fuente
3

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.

Rahul
fuente
99
No necesita utilizar archivos de configuración XML para la inyección de dependencia: elija un contenedor DI que admita la configuración en el código.
Håvard S
8
La inyección de dependencia no implica necesariamente archivos XML. Parece que este todavía puede ser el caso en Java, pero en .NET abandonamos este acoplamiento hace años.
Mark Seemann
66
O no use un contenedor DI en absoluto.
Jesse Millikan
Si sabe que el código está utilizando DI, puede suponer fácilmente quién establece las dependencias.
Bozho
En Java, Guice de Google no necesita archivos XML, por ejemplo.
Epaga
2

Dos cosas:

  • Requieren 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 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.

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

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.

Martin Ellis
fuente
1
Este es el toro absoluto. La inyección de dependencia es un concepto, no requiere un marco de fricken. Todo lo que necesitas es nuevo. Olvídese de la primavera y toda esa basura, entonces sus herramientas funcionarán bien, además podrá refactorizar su código mucho mejor.
time4tea
Es cierto, buen punto. Debería haber sido más claro que estaba hablando de problemas con los marcos DI y no del patrón. Es posible que me haya perdido la aclaración de la pregunta cuando respondí (suponiendo que estuviera allí en ese momento).
Martin Ellis
Ah En cuyo caso, felizmente retracto mi molestia. disculpas
time4tea
2

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.

Chris D
fuente
1

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.

romano
fuente
3
Solo para dar una cifra, un contenedor DI debería resolver miles de dependencias por segundo. ( codinginstinct.com/2008/05/… ) Los contenedores DI permiten la instanciación diferida. El rendimiento (incluso en grandes aplicaciones) no debería ser un problema. O al menos el problema de rendimiento debe ser solucionable y no ser una razón para decidir contra IoC y sus marcos.
Robert