Me resulta difícil buscar recursos sobre por qué debería usar la inyección de dependencia . La mayoría de los recursos que veo explican que simplemente pasa una instancia de un objeto a otra instancia de un objeto, pero ¿por qué? ¿Es esto solo para una arquitectura / código más limpio o esto afecta el rendimiento en general?
¿Por qué debería hacer lo siguiente?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
En lugar de lo siguiente?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
deactivateProfile
me sugiere que establecer el valorisActive
en falso sin preocuparse por su estado anterior es el enfoque correcto aquí. Llamar al método inherentemente significa que quiere establecerlo como inactivo, no obtener su estado activo actual (in).Respuestas:
La ventaja es que sin inyección de dependencia, su clase de perfil
Pero con inyección de dependencia
Esto puede parecer (o incluso ser) irrelevante en este caso en particular, pero imagine que no estamos hablando de un objeto de Configuración, sino de un objeto DataStore, que podría tener implementaciones diferentes, una que almacena datos en archivos y otra que los almacena en una base de datos Y para las pruebas automatizadas, también desea una implementación simulada. Ahora realmente no desea que la clase Profile codifique cuál usa, y lo que es más importante, realmente no desea que la clase Profile conozca las rutas del sistema de archivos, las conexiones DB y las contraseñas, por lo que la creación de objetos DataStore tiene que suceder en otro lugar.
fuente
This may seem (or even be) irrelevant in this particular case
Creo que es muy relevante, de hecho. ¿Cómo obtendrías la configuración? Muchos sistemas que he visto tendrán un conjunto de configuraciones predeterminadas codificadas y una configuración pública, por lo que deberá cargar ambos y sobrescribir algunos valores con la configuración pública. Incluso puede necesitar múltiples fuentes de valores predeterminados. Tal vez incluso pueda obtener algunos del disco, otros de DB. Por lo tanto, toda la lógica para incluso obtener configuraciones puede, y a menudo es, no trivial, definitivamente no es algo que le interese o que le importe al código consumidor.$setting = new Setting();
terriblemente ineficiente. La inyección y la instanciación de objetos ocurre una vez.La inyección de dependencia hace que su código sea más fácil de probar.
Aprendí esto de primera mano cuando me encargaron corregir un error difícil de detectar en la integración de PayPal de Magento.
Surgiría un problema cuando PayPal le informara a Magento sobre un pago fallido: Magento no registraría el error correctamente.
Probar una posible solución "manualmente" sería muy tedioso: tendría que activar de alguna manera una notificación de PayPal "Fallida". Tendría que enviar un cheque electrónico, cancelarlo y esperar a que se produzca un error. ¡Eso significa más de 3 días para probar un cambio de código de un carácter!
Afortunadamente, parece que los desarrolladores principales de Magento que desarrollaron esta función tenían en mente las pruebas y usaron un patrón de inyección de dependencia para hacerlo trivial. Esto nos permite verificar nuestro trabajo con un caso de prueba simple como este:
Estoy seguro de que el patrón DI tiene muchas otras ventajas, pero una mayor capacidad de prueba es el mayor beneficio en mi mente .
Si tiene curiosidad sobre la solución a este problema, consulte el repositorio de GitHub aquí: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
fuente
¿Por qué (cuál es el problema)?
El mejor mnemónico que encontré para esto es " nuevo es pegamento ": cada vez que lo usa
new
en su código, ese código está vinculado a esa implementación específica . Si usa repetidamente new en constructores, creará una cadena de implementaciones específicas . Y debido a que no puede "tener" una instancia de una clase sin construirla, no puede separar esa cadena.Como ejemplo, imagina que estás escribiendo un videojuego de autos de carrera. Comenzaste con una clase
Game
, que crea unRaceTrack
, que crea 8Cars
, que cada uno crea unMotor
. Ahora, si quieres 4 adicionalesCars
con una aceleración diferente, tendrás que cambiar cada clase mencionada , excepto tal vezGame
.Código más limpio
Sí .
Sin embargo, podría parecer menos claro en esta situación, porque es más un ejemplo de cómo hacerlo . La ventaja real solo se muestra cuando hay varias clases involucradas y es más difícil de demostrar, pero imagina que hubieras usado DI en el ejemplo anterior. El código que crea todas esas cosas podría verse así:
Ahora puede agregar esos 4 autos diferentes agregando estas líneas:
RaceTrack
,Game
,Car
, oMotor
eran necesarios - lo que significa que podemos estar 100% seguros de que no ha introducido ningún otro bichos allí!Consideraciones de rendimiento
No se . Pero para ser completamente honesto contigo, podría ser.
Sin embargo, incluso en ese caso, es una cantidad tan ridículamente pequeña que no necesita preocuparse. Si en algún momento en el futuro, usted tiene que escribir código para un tamagotchi con el equivalente de 5Mhz CPU y 2 MB de RAM, entonces tal vez usted puede ser que tenga que preocuparse por esto.
En el 99.999% * de los casos, tendrá un mejor rendimiento, ya que pasó menos tiempo reparando errores y más tiempo mejorando sus algoritmos con muchos recursos.
* número completamente inventado
Información adicional: "codificado"
No se equivoque, esto todavía está muy "codificado": los números se escriben directamente en el código. No codificado significaría algo así como almacenar esos valores en un archivo de texto, por ejemplo, en formato JSON, y luego leerlos desde ese archivo.
Para hacerlo, debe agregar código para leer un archivo y luego analizar JSON. Si considera el ejemplo nuevamente; en la versión no DI, a
Car
o aMotor
ahora tiene que leer un archivo. Eso no parece que tenga demasiado sentido.En la versión DI, lo agregaría al código que configura el juego.
fuente
Siempre me desconcertó la inyección de dependencia. Parecía existir solo dentro de las esferas de Java, pero esas esferas hablaron de ello con gran reverencia. Fue uno de los grandes patrones, ya ves, que se dice que da orden al caos. Pero los ejemplos siempre fueron complicados y artificiales, estableciendo un problema y luego resolviéndolo haciendo el código más complicado.
Tenía más sentido cuando un compañero desarrollador de Python me impartió esta sabiduría: es solo pasar argumentos a las funciones. Es apenas un patrón en absoluto; más como un recordatorio de que usted puede pedir algo como un argumento, incluso si usted podría haber proporcionado concebible un valor razonable a sí mismo.
Entonces, su pregunta es más o menos equivalente a "¿por qué mi función debe tomar argumentos?" y tiene muchas de las mismas respuestas, a saber: dejar que la persona que llama tome decisiones .
Esto tiene un costo, por supuesto, porque ahora está obligando a la persona que llama a tomar alguna decisión (a menos que haga que el argumento sea opcional), y la interfaz es algo más compleja. A cambio, ganas flexibilidad.
Entonces: ¿Hay una buena razón por la que específicamente necesita usar este
Setting
tipo / valor en particular ? ¿Hay una buena razón por la cual el código de llamada podría querer unSetting
tipo / valor diferente ? (¡Recuerde, las pruebas son código !)fuente
El ejemplo que da no es la inyección de dependencia en el sentido clásico. La inyección de dependencia generalmente se refiere a pasar objetos en un constructor o mediante el uso de "inyección de setter" justo después de crear el objeto, para establecer un valor en un campo en un objeto recién creado.
Su ejemplo pasa un objeto como argumento a un método de instancia. Este método de instancia modifica un campo en ese objeto. ¿Inyección de dependencia? No. ¿Romper encapsulación y ocultar datos? ¡Absolutamente!
Ahora, si el código fuera así:
Entonces diría que está utilizando inyección de dependencia. La diferencia notable es que un
Settings
objeto se pasa al constructor o unProfile
objeto.Esto es útil si el objeto Configuración es costoso o complejo de construir, o Configuración es una interfaz o clase abstracta donde existen múltiples implementaciones concretas para cambiar el comportamiento del tiempo de ejecución.
Dado que está accediendo directamente a un campo en el objeto Configuración en lugar de llamar a un método, no puede aprovechar el Polimorfismo, que es uno de los beneficios de la inyección de dependencia.
Parece que la configuración de un perfil es específica de ese perfil. En este caso, haría uno de los siguientes:
Instanciar el objeto de configuración dentro del constructor de perfiles
Pase el objeto Configuración en el constructor y copie sobre campos individuales que se aplican al Perfil
Honestamente, al pasar el objeto Configuración a
deactivateProfile
y luego modificar un campo interno del objeto Configuración es un olor a código. El objeto Configuración debería ser el único que modifique sus campos internos.fuente
The example you give is not dependency injection in the classical sense.
- No importa OO, las personas tienen objetos en el cerebro, pero todavía estás entregando una dependencia a algo.Sé que voy a llegar tarde a esta fiesta, pero siento que se está perdiendo un punto importante.
No deberías Pero no porque la inyección de dependencia sea una mala idea. Es porque esto lo está haciendo mal.
Veamos estas cosas usando código. Vamos a hacer esto:
cuando obtenemos lo mismo de esto:
Entonces, por supuesto, parece una pérdida de tiempo. Es cuando lo haces de esta manera. Este no es el mejor uso de la inyección de dependencia. Ni siquiera es el mejor uso de una clase.
Ahora, ¿y si en cambio tuviéramos esto?
Y ahora
application
es libre de activar y desactivarprofile
sin tener que saber nada en particular acerca de losetting
que realmente está cambiando. ¿Por qué es eso bueno? En caso de que necesite cambiar la configuración. Elapplication
está amurallado fuera de esos cambios por lo que es libre de ir tuercas en un espacio contenido seguro sin tener que mirar todo romper tan pronto como se toca algo.Esto sigue la construcción separada del principio de comportamiento . El patrón DI aquí es simple. Construya todo lo que necesite al nivel más bajo posible, conéctelos y luego comience a marcar todo el comportamiento con una sola llamada.
El resultado es que tiene un lugar separado para decidir qué se conecta con qué y un lugar diferente para administrar lo que dice qué a lo que sea.
Intente eso en algo que tiene que mantener con el tiempo y vea si no ayuda.
fuente
Como cliente, cuando contrata a un mecánico para que le haga algo a su automóvil, ¿espera que ese mecánico construya un automóvil desde cero solo para luego trabajar con él? No, le das al mecánico el auto en el que quieres que trabajen .
Como propietario del garaje, cuando le indica a un mecánico que le haga algo a un automóvil, ¿espera que el mecánico cree sus propias piezas de destornillador / llave / automóvil? No, usted le proporciona al mecánico las piezas / herramientas que necesita usar
¿Por qué hacemos esto? Bueno, piensalo. Eres propietario de un garaje que quiere contratar a alguien para que se convierta en tu mecánico. Les enseñarás a ser mecánicos (= escribirás el código).
¿Qué va a ser más fácil?
Hay enormes beneficios de no hacer que tu mecánico cree todo desde cero:
Si contrata y entrena el experto mecánico, que va a terminar con un empleado que cuesta más, toma más tiempo para llevar a cabo lo que debe ser un trabajo sencillo, y necesitará ser perpetuamente nueva formación cada vez que una de sus muchas responsabilidades necesitan Para actualizarse.
La analogía del desarrollo es que si usa clases con dependencias codificadas, entonces terminará con clases difíciles de mantener que necesitarán una reurbanización / cambios continuos cada vez que
Settings
se cree una nueva versión del objeto ( en su caso), y usted Tendrá que desarrollar una lógica interna para que la clase tenga la capacidad de crear diferentes tipos deSettings
objetos.Además, quien consuma su clase ahora también tendrá que pedirle a la clase que cree el
Settings
objeto correcto , en lugar de simplemente poder pasarle a la clase cualquierSettings
objeto que desee pasar. Esto significa un desarrollo adicional para que el consumidor descubra cómo pedirle a la clase que cree la herramienta adecuada.Sí, la inversión de dependencia requiere un poco más de esfuerzo para escribir en lugar de codificar la dependencia. Sí, es molesto tener que escribir más.
Pero ese es el mismo argumento que elegir codificar valores literales porque "declarar variables requiere más esfuerzo". Técnicamente correcto, pero los profesionales superan a los contras en varios órdenes de magnitud .
El beneficio de la inversión de dependencia no se experimenta cuando crea la primera versión de la aplicación. El beneficio de la inversión de dependencia se experimenta cuando necesita cambiar o ampliar esa versión inicial. Y no se engañe pensando que lo hará bien la primera vez y que no necesitará extender / cambiar el código. Tendrás que cambiar las cosas.
Esto no afecta el rendimiento en tiempo de ejecución de la aplicación. Pero impacta masivamente el tiempo de desarrollo (y por lo tanto el rendimiento) del desarrollador .
fuente
Como todos los patrones, es muy válido preguntar "por qué" para evitar diseños hinchados.
Para la inyección de dependencia, esto se ve fácilmente al pensar en las dos, posiblemente, las facetas más importantes del diseño OOP ...
Bajo acoplamiento
Acoplamiento en programación de computadoras :
Desea lograr un bajo acoplamiento. El hecho de que dos cosas estén fuertemente acopladas significa que si cambia una, es muy probable que tenga que cambiar la otra. Los errores o restricciones en uno probablemente inducirán errores / restricciones en el otro; y así.
Una clase de objetos de instancia de los otros es un acoplamiento muy fuerte, porque uno necesita saber acerca de la existencia del otro; necesita saber cómo instanciarlo (qué argumentos necesita el constructor), y esos argumentos deben estar disponibles al llamar al constructor. Además, dependiendo de si el lenguaje necesita deconstrucción explícita (C ++), esto introducirá más complicaciones. Si introduce nuevas clases (es decir, a
NextSettings
o lo que sea), debe volver a la clase original y agregar más llamadas a sus constructores.Alta cohesión
Cohesión :
Esta es la otra cara de la moneda. Si observa una unidad de código (un método, una clase, un paquete, etc.), desea tener todo el código dentro de esa unidad para tener la menor cantidad de responsabilidades posible.
Un ejemplo básico para esto sería el patrón MVC: separa claramente el modelo de dominio de la vista (GUI) y una capa de control que los une.
Esto evita la hinchazón de código donde se obtienen grandes fragmentos que hacen muchas cosas diferentes; si desea cambiar alguna parte, también debe realizar un seguimiento de todas las demás funciones, tratando de evitar errores, etc .; y rápidamente te programas en un agujero donde es muy difícil salir.
Con la inyección de dependencias, delega la creación o el seguimiento de, bueno, dependencias a cualquier clase (o archivo de configuración) que implemente su DI. A otras clases no les importará mucho lo que está sucediendo exactamente: trabajarán con algunas interfaces genéricas y no tienen idea de cuál es la implementación real, lo que significa que no son responsables de las otras cosas.
fuente
Debes usar técnicas para resolver los problemas que son buenos para resolver cuando tienes esos problemas. La inversión de dependencia y la inyección no son diferentes.
La inversión o inyección de dependencia es una técnica que le permite a su código decidir qué implementación de un método se llama en tiempo de ejecución. Esto maximiza los beneficios de la unión tardía. La técnica es necesaria cuando el lenguaje no admite el reemplazo en tiempo de ejecución de funciones que no son de instancia. Por ejemplo, Java carece de un mecanismo para reemplazar las llamadas a un método estático con llamadas a una implementación diferente; contraste con Python, donde todo lo que es necesario para reemplazar la llamada a la función es vincular el nombre a una función diferente (reasignar la variable que contiene la función).
¿Por qué querríamos variar la implementación de la función? Hay dos razones principales:
También puede tomar nota de la inversión de los contenedores de control. Esta es una técnica destinada a ayudarlo a evitar árboles de construcción enormes y enredados que se parecen a este pseudocódigo:
Te permite registrar tus clases y luego hace la construcción por ti:
Tenga en cuenta que es más simple si las clases registradas pueden ser singletons sin estado .
Palabra de precaución
Tenga en cuenta que la inversión de dependencia no debería ser su respuesta a la lógica de desacoplamiento. Busque oportunidades para utilizar la parametrización en su lugar. Considere este método de pseudocódigo, por ejemplo:
Podríamos usar la inversión de dependencia para algunas partes de este método:
Pero no deberíamos, al menos no completamente. Tenga en cuenta que hemos creado una clase con estado con
Querier
. Ahora contiene una referencia a algún objeto de conexión esencialmente global. Esto crea problemas como la dificultad para comprender el estado general del programa y cómo las diferentes clases se coordinan entre sí. Tenga en cuenta también que estamos obligados a falsificar el interrogador o la conexión si queremos probar la lógica de promedio. Además, un mejor enfoque sería aumentar la parametrización :Y la conexión se administraría a un nivel aún mayor que es responsable de la operación en su conjunto y sabe qué hacer con esta salida.
Ahora podemos probar la lógica de promedios completamente independiente de la consulta, y además, podemos usarla en una variedad más amplia de situaciones. Podríamos preguntarnos si incluso necesitamos los objetos
MyQuerier
yAverager
, y tal vez la respuesta es que no lo hacemos si no pretendemos realizar una prueba unitariaStuffDoer
, y no la prueba unitariaStuffDoer
sería perfectamente razonable ya que está tan estrechamente acoplada a la base de datos. Puede tener más sentido dejar que las pruebas de integración lo cubran. En ese caso, podría estar haciendo bienfetchAboveMin
yaverageData
en los métodos estáticos.fuente