¿Por qué debería usar la inyección de dependencia?


95

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;
    }
}

8
Está introduciendo una dependencia codificada para desactivar el perfil () (que es malo). Tiene más código desacoplado en el primero, lo que facilita el cambio y la prueba.
Aulis Ronkainen

3
¿Por qué harías el primero? Estás pasando una configuración y luego ignoras su valor.
Phil N DeBlanc

57
No estoy de acuerdo con los votos negativos. Si bien el tema puede considerarse trivial para los expertos, la pregunta tiene mérito: si se debe utilizar la inversión de dependencia, entonces debe haber una justificación para usarlo.
Flater

13
@PhilNDeBlanc: este código está claramente simplificado en exceso y no es realmente indicativo de la lógica del mundo real. Sin embargo, deactivateProfileme sugiere que establecer el valor isActiveen 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).
Flater

2
Su código no es un ejemplo de inyección de dependencia o inversión. Es un ejemplo de parametrización (que a menudo es mucho mejor que DI).
jpmc26

Respuestas:


104

La ventaja es que sin inyección de dependencia, su clase de perfil

  • necesita saber cómo crear un objeto de Configuración (viola el Principio de responsabilidad única)
  • Siempre crea su objeto Configuración de la misma manera (crea un acoplamiento estrecho entre los dos)

Pero con inyección de dependencia

  • La lógica para crear objetos de configuración está en otro lugar
  • Es fácil usar diferentes tipos de objetos de configuración

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.


23
This may seem (or even be) irrelevant in this particular caseCreo 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.
VLAZ

También podríamos mencionar que la inicialización de objetos para un componente no trivial, como un servicio web, sería $setting = new Setting();terriblemente ineficiente. La inyección y la instanciación de objetos ocurre una vez.
vikingsteve

99
Creo que usar simulacros para las pruebas debería tener más énfasis. Lo más probable es que si solo mira el código, siempre será un objeto de Configuración y nunca cambiará, por lo que pasarlo parece un esfuerzo perdido. Sin embargo, la primera vez que intenta probar un objeto de perfil por sí mismo sin necesidad de un objeto de configuración (utilizando un objeto simulado en su lugar como solución) la necesidad es muy evidente.
JPhi1618

2
@ JPhi1618 Creo que el problema de enfatizar "DI es para pruebas unitarias" es que simplemente lleva a la pregunta "¿por qué necesito pruebas unitarias?". La respuesta puede parecer obvia para usted, y los beneficios definitivamente están ahí, pero para alguien que recién comienza, diciendo "necesita hacer esta cosa de sonido complicado para hacer esta otra cosa de sonido complicado" tiende a ser un poco un desvío Por lo tanto, es bueno mencionar diferentes ventajas que podrían ser más aplicables a lo que están haciendo en este momento.
IMSoP

1
@ user986730: ese es un muy buen punto, y destaca que el principio real aquí es la Inversión de dependencias , de la cual la Inyección es solo una técnica. inviértalos en tiempo de compilación, incluyendo diferentes archivos fuente para sus simulacros, etc., lo que tiene el mismo efecto.
Stephen Byrne el

64

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:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

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


44
La inyección de dependencias hace que el código sea más fácil de probar que el código con dependencias codificadas. Eliminar las dependencias de la lógica empresarial por completo es aún mejor.
Ant P

1
Y una forma importante de hacer lo que @AntP sugiere es mediante la parametrización . La lógica para transformar los resultados que regresan de la base de datos en el objeto utilizado para completar una plantilla de página (comúnmente conocido como "modelo") ni siquiera debería saber que está ocurriendo una recuperación; solo necesita obtener esos objetos como entrada.
jpmc26

44
@ jpmc26, de hecho: tiendo a insistir sobre el núcleo funcional, el shell imperativo , que en realidad es solo un nombre elegante para pasar datos a su dominio en lugar de inyectar dependencias en él. El dominio es puro, se puede probar en la unidad sin simulacros y luego se envuelve en un caparazón que adapta cosas como persistencia, mensajes, etc.
Ant P

1
Creo que el único enfoque en la capacidad de prueba es perjudicial para la adopción de DI. Hace que no sea atractivo para las personas que sienten que no necesitan muchas pruebas o piensan que ya las tienen bajo control. Yo diría que es prácticamente imposible escribir código limpio y reutilizable sin DI. Las pruebas están muy por debajo de la lista de beneficios y es decepcionante ver que esta respuesta ocupa el primer o segundo lugar en cada pregunta sobre los beneficios de DI.
Carl Leth el

26

¿Por qué (cuál es el problema)?

¿Por qué debería usar la inyección de dependencia?

El mejor mnemónico que encontré para esto es " nuevo es pegamento ": cada vez que lo usa newen 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 un RaceTrack, que crea 8 Cars, que cada uno crea un Motor. Ahora, si quieres 4 adicionales Carscon una aceleración diferente, tendrás que cambiar cada clase mencionada , excepto tal vez Game.

Código más limpio

¿Es esto solo para una arquitectura / código más limpio?

.

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

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

Ahora puede agregar esos 4 autos diferentes agregando estas líneas:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • No hay cambios en RaceTrack, Game, Car, o Motoreran necesarios - lo que significa que podemos estar 100% seguros de que no ha introducido ningún otro bichos allí!
  • En lugar de tener que saltar entre varios archivos, podrá ver el cambio completo en su pantalla al mismo tiempo. Esto es el resultado de ver la creación / instalación / configuración como una responsabilidad propia : no es el trabajo de un automóvil construir un motor.

Consideraciones de rendimiento

o esto afecta el rendimiento en su conjunto?

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 Caro a Motorahora 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.


3
Anuncio codificado, no hay mucha diferencia entre el código y el archivo de configuración. Un archivo incluido con la aplicación es una fuente, incluso si lee de forma dinámica. Extraer valores del código a los archivos de datos en json o cualquier formato 'config' no ayuda en nada, a menos que el usuario anule los valores o dependa del entorno.
Jan Hudec

3
Realmente hice un Tamagotchi una vez en un Arduino (16MHz, 2KB)
Jungkook

@JanHudec Cierto. De hecho, tuve una explicación más larga allí, pero decidí eliminarla para acortarla y centrarme en cómo se relaciona con la DI. Hay más cosas que no son 100% correctas; En general, la respuesta está más optimizada para impulsar "el punto" de DI sin que sea demasiado largo. O dicho de otra manera, esto es lo que me hubiera gustado escuchar cuando comencé con DI.
R. Schmitz

17

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 Settingtipo / valor en particular ? ¿Hay una buena razón por la cual el código de llamada podría querer un Settingtipo / valor diferente ? (¡Recuerde, las pruebas son código !)


2
Sí. IoC finalmente me hizo clic cuando me di cuenta de que se trata simplemente de "dejar que la persona que llama tome decisiones". Ese IoC significa que el control del componente se transfiere del autor del componente al usuario del componente. Y dado que en ese momento ya he tenido suficientes quejas con un software que se consideraba más inteligente que yo, me vendieron instantáneamente con el enfoque DI.
Joker_vD

1
"es solo pasar argumentos a las funciones. Es apenas un patrón en absoluto" Esta es la única explicación buena o incluso comprensible de DI que he escuchado (bueno, la mayoría de las personas ni siquiera hablan de lo que es , sino de sus beneficios). Y luego usan jerga complicada como "inversión de control" y "acoplamiento flojo" que solo requieren más preguntas para entender cuando es una idea realmente simple.
Addison

7

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

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Entonces diría que está utilizando inyección de dependencia. La diferencia notable es que un Settingsobjeto se pasa al constructor o un Profileobjeto.

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:

  1. Instanciar el objeto de configuración dentro del constructor de perfiles

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


55
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.
Robert Harvey

1
Cuando hablas de " en el sentido clásico ", estás, como dice @RobertHarvey, hablando exclusivamente en términos OO. En la programación funcional, por ejemplo, inyectar una función en otra (funciones de orden superior) es el ejemplo clásico de inyección de dependencia de ese paradigma.
David Arno

3
@RobertHarvey: Supongo que me estaba tomando demasiadas libertades con la "inyección de dependencia". El lugar donde la gente piensa más a menudo en el término y lo usa es en referencia a los campos en un objeto que se "inyecta" en el momento de la construcción del objeto, o inmediatamente después en el caso de la inyección de setter.
Greg Burghardt el

@DavidArno: Sí, tienes razón. El OP parece tener un fragmento de código PHP orientado a objetos en la pregunta, por lo que respondía solo en ese sentido y no abordaba la programación funcional, aunque con PHP podría hacer la misma pregunta desde el punto de vista de la programación funcional.
Greg Burghardt el

7

Sé que voy a llegar tarde a esta fiesta, pero siento que se está perdiendo un punto importante.

Por qué debería hacer esto:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

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:

$profile = new Profile();
$profile->deactivateProfile($setting);

cuando obtenemos lo mismo de esto:

$setting->isActive = false; // Deactivate profile

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?

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

Y ahora applicationes libre de activar y desactivar profilesin tener que saber nada en particular acerca de lo settingque realmente está cambiando. ¿Por qué es eso bueno? En caso de que necesite cambiar la configuración. El applicationestá 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.


6

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?

  • Enseñe a un mecánico cómo conectar un alerón a un automóvil con un destornillador.
  • Enseñe a un mecánico a crear un automóvil, crear un spoiler, crear un destornillador y luego conectar el spoiler recién creado al automóvil recién creado con el destornillador recién creado.

Hay enormes beneficios de no hacer que tu mecánico cree todo desde cero:

  • Obviamente, la capacitación (= desarrollo) se acorta drásticamente si solo le proporciona a su mecánico las herramientas y piezas existentes.
  • Si el mismo mecánico tiene que realizar el mismo trabajo varias veces, puede asegurarse de que vuelva a usar el destornillador en lugar de tirar siempre el viejo y crear uno nuevo.
  • Además, un mecánico que ha aprendido a crear todo necesitará ser mucho más experto y, por lo tanto, esperará un salario más alto. La analogía de codificación aquí es que una clase con muchas responsabilidades es mucho más difícil de mantener que una clase con una sola responsabilidad estrictamente definida.
  • Además, cuando los nuevos inventos llegan al mercado y los spoilers ahora se fabrican con carbono en lugar de plástico; Tendrá que volver a entrenar (= volver a desarrollar) su mecánico experto. Pero su mecánico "simple" no tendrá que volverse a entrenar siempre y cuando el spoiler todavía se pueda conectar de la misma manera.
  • Tener un mecánico que no confía en un automóvil que ellos mismos han construido significa que usted tiene un mecánico que puede manejar cualquier automóvil que pueda recibir. Incluyendo autos que ni siquiera existían al momento de entrenar al mecánico. Sin embargo, su mecánico experto no podrá construir autos más nuevos que hayan sido creados después de su entrenamiento.

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 Settingsse 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 de Settingsobjetos.

Además, quien consuma su clase ahora también tendrá que pedirle a la clase que cree el Settingsobjeto correcto , en lugar de simplemente poder pasarle a la clase cualquier Settingsobjeto 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 afecta el rendimiento en su conjunto?

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 .


2
Si eliminó su comentario, " Como comentario menor, su pregunta se enfoca en la inversión de dependencia, no en la inyección de dependencia. La inyección es una forma de hacer inversión, pero no es la única " . Esta sería una excelente respuesta. La inversión de dependencia se puede lograr mediante inyección o localizador / globals. Los ejemplos de la pregunta se refieren a la inyección. Entonces, la pregunta es sobre la inyección de dependencia (así como la inversión de dependencia).
David Arno el

12
Creo que todo el asunto del automóvil es un poco confuso y confuso
Ewan

44
@Flater, parte del problema es que nadie parece estar de acuerdo sobre cuál es la diferencia entre la inyección de dependencia, la inversión de dependencia y la inversión de control. Sin embargo, una cosa es segura, un "contenedor" ciertamente no es necesario para inyectar una dependencia en un método o constructor. La DI pura (o pobre) describe específicamente la inyección manual de dependencia. Es la única inyección de dependencia que uso personalmente, ya que no me gusta la "magia" asociada con los contenedores.
David Arno

1
@Flater El problema con el ejemplo es que seguramente no modelarías ningún problema en el código de esta manera. Por lo tanto, es antinatural, forzado y agrega más preguntas que respuestas. Eso hace que sea más probable confundir y confundir que demostrar.
jpmc26

2
@gbjbaanb: en ningún momento mi respuesta profundiza remotamente en el polimorfismo. No hay herencia, implementación de interfaz ni nada parecido a la subida / bajada de tipos. Estás completamente malinterpretando la respuesta.
Flater

0

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 :

En ingeniería de software, el acoplamiento es el grado de interdependencia entre los módulos de software; una medida de cuán estrechamente conectados están dos rutinas o módulos; La fuerza de las relaciones entre los módulos.

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 NextSettingso lo que sea), debe volver a la clase original y agregar más llamadas a sus constructores.

Alta cohesión

Cohesión :

En la programación de computadoras, la cohesión se refiere al grado en que los elementos dentro de un módulo pertenecen juntos.

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.


-1

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:

  • Queremos usar falsificaciones con fines de prueba. Esto nos permite probar una clase que depende de una búsqueda de la base de datos sin conectarse realmente a la base de datos.
  • Necesitamos soportar múltiples implementaciones. Por ejemplo, podríamos necesitar configurar un sistema que sea compatible con las bases de datos MySQL y PostgreSQL.

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:

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

Te permite registrar tus clases y luego hace la construcción por ti:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

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:

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

Podríamos usar la inversión de dependencia para algunas partes de este método:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

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 :

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

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 MyQueriery Averager, y tal vez la respuesta es que no lo hacemos si no pretendemos realizar una prueba unitaria StuffDoer, y no la prueba unitaria StuffDoerserí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 bien fetchAboveMiny averageDataen los métodos estáticos.


2
" La inyección de dependencia es una técnica destinada a ayudarlo a evitar árboles de construcción enormes y enredados ... ". Su primer ejemplo después de este reclamo es un ejemplo de inyección de dependencia pura o pobre. El segundo es un ejemplo del uso de un contenedor IoC para "automatizar" la inyección de esas dependencias. Ambos son ejemplos de inyección de dependencia en acción.
David Arno el

@DavidArno Sí, tienes razón. He ajustado la terminología.
jpmc26

Hay una tercera razón principal para variar la implementación, o al menos diseñar el código asumiendo que la implementación puede variar: incentiva al desarrollador a tener un acoplamiento flojo y evitar escribir código que será difícil de cambiar si se implementa una nueva implementación en algún momento el futuro. Y aunque eso puede no ser una prioridad en algunos proyectos (por ejemplo, sabiendo que la aplicación nunca será revisada después de su lanzamiento inicial), lo será en otros (por ejemplo, donde el modelo de negocio de la compañía intenta específicamente ofrecer soporte / extensión extendida de sus aplicaciones )
Flater

La inyección de dependencia de @Flater todavía da como resultado un fuerte acoplamiento. Vincula la lógica a una interfaz en particular y requiere que el código en cuestión sepa lo que hace esa interfaz. Considere el código que transforma los resultados obtenidos de una base de datos. Si uso DI para separarlos, entonces el código aún necesita saber que ocurre una recuperación de DB e invocarlo. La mejor solución es si el código de transformación ni siquiera sabe que está ocurriendo una recuperación . La única forma de hacerlo es si una persona que llama pasa los resultados de la búsqueda, en lugar de inyectarlos.
jpmc26

1
@ jpmc26 Pero en su (comentario) ejemplo, la base de datos ni siquiera necesitaba ser una dependencia para empezar. Por supuesto, evitar dependencias es mejor para un acoplamiento flexible, pero DI se centra en implementar las dependencias que se necesitan.
Flater
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.