Hay un viejo, viejo debate en curso sobre la mejor manera de hacer la inyección de dependencia.
El corte original del resorte instanciaba un objeto plano y luego inyectaba dependencias a través de métodos de establecimiento.
Pero luego una gran contingencia de personas insistió en que inyectar dependencias a través de parámetros de constructor era la forma correcta de hacerlo.
Luego, últimamente, a medida que el uso de la reflexión se hizo más común, establecer los valores de los miembros privados directamente, sin establecedores ni argumentos de constructor, se convirtió en la rabia.
Entonces, su primer constructor es consistente con el segundo enfoque para la inyección de dependencia. Le permite hacer cosas buenas como inyectar simulacros para probar.
Pero el constructor sin argumentos tiene este problema. Dado que está creando instancias de las clases de implementación para PaypalCreditCardProcessor
y DatabaseTransactionLog
, crea una dependencia difícil en tiempo de compilación de PayPal y la Base de datos. Asume la responsabilidad de construir y configurar todo ese árbol de dependencias correctamente.
Imagine que el procesador PayPay es un subsistema realmente complicado, y además incorpora muchas bibliotecas de soporte. Al crear una dependencia en tiempo de compilación en esa clase de implementación, está creando un enlace irrompible a todo ese árbol de dependencias. La complejidad de su gráfico de objeto acaba de aumentar en un orden de magnitud, tal vez dos.
Muchos de esos elementos en el árbol de dependencias serán transparentes, pero muchos de ellos también deberán ser instanciados. Lo más probable es que no puedas crear una instancia de a PaypalCreditCardProcessor
.
Además de la creación de instancias, cada uno de los objetos necesitará propiedades aplicadas desde la configuración.
Si solo tiene una dependencia en la interfaz y permite que una fábrica externa construya e inyecte la dependencia, se corta todo el árbol de dependencias de PayPal y la complejidad de su código se detiene en la interfaz.
Hay otros beneficios, como especificar clases de implementaciones en la configuración (es decir, en tiempo de ejecución en lugar de tiempo de compilación), o tener una especificación de dependencia más dinámica que varía, por ejemplo, por entorno (prueba, integración, producción).
Por ejemplo, supongamos que PayPalProcessor tenía 3 objetos dependientes, y cada una de esas dependencias tenía dos más. Y todos esos objetos tienen que extraer propiedades de la configuración. El código tal como está asumiría la responsabilidad de construir todo eso, establecer propiedades a partir de la configuración, etc. etc., todas las preocupaciones de las que se ocupará el marco DI.
Al principio puede no parecer obvio de qué se está protegiendo al usar un marco DI, pero se acumula y se vuelve dolorosamente obvio con el tiempo. (jaja, hablo desde la experiencia de haber intentado hacerlo de la manera difícil)
...
En la práctica, incluso para un programa realmente pequeño, encuentro que termino escribiendo en un estilo DI y divido las clases en pares de implementación / fábrica. Es decir, si no estoy usando un marco DI como Spring, solo agrego algunas clases simples de fábrica.
Eso proporciona la separación de las preocupaciones para que mi clase pueda hacer lo suyo, y la clase de fábrica asume la responsabilidad de construir y configurar cosas.
No es un enfoque obligatorio, pero FWIW
...
En términos más generales, el patrón DI / interfaz reduce la complejidad de su código al hacer dos cosas:
Además de eso, dado que la creación de instancias y configuración de objetos es una tarea bastante familiar, el marco DI puede lograr muchas economías de escala a través de la notación estandarizada y el uso de trucos como la reflexión. Dispersar esas mismas preocupaciones alrededor de las clases termina agregando mucho más desorden de lo que uno pensaría.