He utilizado varios contenedores IoC (Castle.Windsor, Autofac, MEF, etc.) para .Net en varios proyectos. He descubierto que tienden a ser abusados con frecuencia y alientan una serie de malas prácticas.
¿Existen prácticas establecidas para el uso de contenedores IoC, particularmente cuando se proporciona una plataforma / marco? Mi objetivo como escritor de marcos es hacer que el código sea lo más simple y fácil de usar posible. Prefiero escribir una línea de código para construir un objeto que diez o incluso solo dos.
Por ejemplo, un par de olores de código que he notado y no tengo buenas sugerencias para:
Gran cantidad de parámetros (> 5) para constructores. Crear servicios tiende a ser complejo; Todas las dependencias se inyectan a través del constructor, a pesar de que los componentes rara vez son opcionales (excepto tal vez en las pruebas).
Falta de clases privadas e internas; Esta puede ser una limitación específica del uso de C # y Silverlight, pero estoy interesado en cómo se resuelve. Es difícil saber qué es una interfaz de framework si todas las clases son públicas; me permite acceder a partes privadas que probablemente no debería tocar.
Acoplamiento del ciclo de vida del objeto al contenedor de IoC. A menudo es difícil construir manualmente las dependencias necesarias para crear objetos. El marco de trabajo de IoC gestiona con demasiada frecuencia el ciclo de vida del objeto He visto proyectos donde la mayoría de las clases están registradas como Singletons. Obtiene una falta de control explícito y también se ve obligado a administrar los elementos internos (se relaciona con el punto anterior, todas las clases son públicas y debe inyectarlas).
Por ejemplo, .Net framework tiene muchos métodos estáticos. como DateTime.UtcNow. Muchas veces he visto esto envuelto e inyectado como un parámetro de construcción.
Dependiendo de la implementación concreta, mi código es difícil de probar. Inyectar una dependencia hace que mi código sea difícil de usar, especialmente si la clase tiene muchos parámetros.
¿Cómo proporciono una interfaz comprobable y una fácil de usar? ¿Cuáles son las mejores prácticas?
Respuestas:
El único anti-patrón legítimo de inyección de dependencia que conozco es el Localizador de servicios patrón , que es un antipatrón cuando se usa un marco DI para él.
Todos los otros llamados antipatrones de DI que he escuchado, aquí o en otros lugares, son casos un poco más específicos de antipatrones generales de diseño de software / OO. Por ejemplo:
La sobreinyección del constructor es una violación del Principio de responsabilidad única . Demasiados argumentos de constructor indican demasiadas dependencias; Demasiadas dependencias indican que la clase está tratando de hacer demasiado. Por lo general, este error se correlaciona con otros olores de código, como nombres de clase inusualmente largos o ambiguos ("administrador"). Las herramientas de análisis estático pueden detectar fácilmente el acoplamiento aferente / eferente excesivo.
La inyección de datos, en oposición al comportamiento, es un subtipo del antipatrón poltergeist , siendo el 'geist en este caso el contenedor. Si una clase necesita estar al tanto de la fecha y hora actuales, no se inyecta un
DateTime
, que es datos; en su lugar, inyecta una abstracción sobre el reloj del sistema (generalmente llamo al míoISystemClock
, aunque creo que hay uno más general en el proyecto SystemWrappers ). Esto no solo es correcto para DI; es absolutamente esencial para la capacidad de prueba, para que pueda probar funciones que varían en el tiempo sin tener que esperarlas.Declarar cada ciclo de vida como Singleton es, para mí, un ejemplo perfecto de programación de culto de carga y, en menor grado, el " pozo negro de objetos " denominado coloquialmente . He visto más abuso de singleton del que me gustaría recordar, y muy poco de eso involucra DI.
Otro error común son los tipos de interfaz específicos de la implementación (con nombres extraños como
IOracleRepository
) realizados solo para poder registrarlo en el contenedor. Esto es en sí mismo una violación del Principio de Inversión de Dependencia (solo porque es una interfaz, no significa que sea realmente abstracto) y, a menudo, también incluye una hinchazón de la interfaz que viola el Principio de Segregación de la Interfaz .El último error que generalmente veo es la "dependencia opcional", que hicieron en NerdDinner . En otras palabras, hay un constructor que acepta la inyección de dependencia, pero también otro constructor que usa una implementación "predeterminada". Esto también viola el DIP y tiende a conducir a violaciones de LSP , ya que los desarrolladores, con el tiempo, comienzan a hacer suposiciones sobre la implementación predeterminada y / o comienzan instancias nuevas utilizando el constructor predeterminado.
Como dice el viejo dicho, puedes escribir FORTRAN en cualquier idioma . La Inyección de dependencias no es una bala de plata que evitará que los desarrolladores arruinen su administración de dependencias, pero sí evita una serie de errores / antipatrones comunes:
...y así.
Obviamente, no desea diseñar un marco para depender de una implementación específica del contenedor de IoC , como Unity o AutoFac. Es decir, una vez más, violar el DIP. Pero si te encuentras incluso pensando en hacer algo así, entonces ya debes haber cometido varios errores de diseño, porque la inyección de dependencias es una técnica de gestión de dependencias de propósito general y no vinculada al concepto de un contenedor IoC.
Cualquier cosa puede construir un árbol de dependencia; tal vez es un contenedor de IoC, tal vez es una prueba unitaria con un montón de simulacros, tal vez es un controlador de prueba que proporciona datos ficticios. Su marco no debería importarle, y a la mayoría de los marcos que he visto no les importa, pero aún así hacen un uso intensivo de la inyección de dependencia para que pueda integrarse fácilmente en el contenedor de IoC elegido por el usuario final.
DI no es ciencia espacial. Solo trate de evitar
new
ystatic
excepto cuando haya una razón convincente para usarlos, como un método de utilidad que no tenga dependencias externas o una clase de utilidad que posiblemente no tenga ningún propósito fuera del marco (los contenedores de interoperabilidad y las claves de diccionario son ejemplos comunes de esta).Muchos de los problemas con los marcos de IoC surgen cuando los desarrolladores aprenden por primera vez cómo usarlos, y en lugar de cambiar realmente la forma en que manejan las dependencias y las abstracciones para adaptarse al modelo de IoC, intentan manipular el contenedor de IoC para cumplir con las expectativas de sus clientes. estilo de codificación antiguo, que a menudo implicaría un alto acoplamiento y baja cohesión. El código incorrecto es un código incorrecto, ya sea que use técnicas DI o no.
fuente