Inyección de dependencias, Inversión de dependencias e inversión de control.

Para entender el valor de estos conceptos y cuando aplicarlo, primero debemos entender que es el acoplamiento, el cual es una medida en la que un componente o clase depende de otro para funcionar. Debido a que nuestros proyectos de software poseen diferentes componentes y clases para trabajar interactuando entre ellas para entregar un resultado, es muy difícil y prácticamente imposible tener un software sin acoplamiento; sin embargo, siempre se debe buscar tener el acoplamiento en una medida baja.
Veamos el siguiente ejemplo:

Figura 1.0 MoneyTransferService como una clase con múltiples dependencias y un alto acoplamiento

Nuestra clase de MoneyTransferService es una clase muy inestable, dado que tiene muchas dependencias que pueden hacer que nuestra clase necesite cambios. Adicionalmente tiene un alto acoplamiento al estar ligado directamente a clases concretas que impiden poder llegar a cambiar una clase sin verse directamente afectada.

Es en estos escenarios donde aparece la inyección de Dependencias y la inversión de dependencias para ayudarnos a reducir el acoplamiento y brindar un poco más estabilidad a nuestras clases.

Inyección de dependencias

La inyección de dependencia desde un punto de vista teórico es una técnica utilizada para suministrar las instancias de objetos a una clase en lugar de que esta ser la encargada de crearlos. 

Veamos el siguiente ejemplo:

Figura 2.0 Clase Librería que tiene un método para vender libro 

Observamos como la clase Library tiene un acoplamiento ‘leve’ debido a que la cantidad de clases de las que depende no son muchas.

Figura 2.1 Clases encargada de hacer la entrega del libro y otra encargada del guardado de la venta

Sin embargo, pese a ese ‘leve’ acoplamiento, se tiene la responsabilidad de instanciar el objeto y por lo tanto, aumentar más el acoplamiento de la clase al no solo depender de ellas sino tener la responsabilidad de su creación.

Por lo tanto, con la inyección de dependencias buscamos que las instancias de la clases que necesitamos nos sean suministradas en nuestras clases y así lograr evitar crear objetos en ella. Veamos cómo lograrlo.

2.2 LibraryService aplica inyección de dependencias para suministrar las instancias necesarias para funcionar.

Sin embargo, es momento de hacernos una pregunta ¿Se redujo el acoplamiento que tenía la clase LibraryService? la respuesta es sencillamente NO. Nuestro código sigue estando dependiendo de las clases DeliveryEmail e LibraryRepository para lograr su funcionamiento.

Nuestra clase (LibraryService) adicional a tener la lógica de negocio necesaria para la venta del libro se acopla también al cómo emitir una factura y cómo enviar un pedido. Por lo tanto, el patrón de diseño inyección de dependencias per se no nos ayuda a reducir el acoplamiento en nuestro software.

Tenga en cuenta que existen dos formas de inyectar la dependencia: una por constructor como se vio en el ejemplo y la otra por parámetro de una función que necesite de una dependencia para funcionar.

Ahora veamos qué técnica nos ayudará a reducir el acoplamiento hacia esos módulos de bajo nivel necesarios en nuestra aplicación para funcionar.

Inversión de dependencias

El principio de inversión de dependencias (también conocido como DIP por sus siglas del inglés Dependency Inversion Principle)  forma parte de los principales principios de desarrollo orientado a objetos y diseño; SOLID. 

DIP se define formalmente por Robert C. Martín por primera vez a partir de las siguientes declaraciones:

  1. Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.

Cuando se habla de ‘módulo de alto nivel’ se refiere a esas clases que tienen la lógica de negocio y define lo que hace tu programa; estos módulos de alto nivel deberían ser los más importantes en tu aplicación. 

Por otro lado están los módulos de bajo nivel que contienen aquellas clases que no están ligadas directamente a la lógica del negocio como lo puede llegar a ser la persistencia de datos (SQL, NoSQL), o la comunicación con un servicio externo a través (rest, soap u otros mecanismos). El ideal es permitir que estos módulos de bajo nivel puedan ser reemplazables sin afectar la lógica de nuestro negocio. 

  1. Las abstracciones no deben depender de los detalles. Los detalles deben depender de abstracciones.

Para lograr esta afirmación, nuestra clase ubicada en módulo de alto nivel dependerá de una abstracción; así mismo, la clase de bajo nivel dependerá de esta abstracción al momento de heredar de ella. Veámoslo en un gráfico nuestro panorama actual:

2.3 La clase LibraryService tiene una relación de dependencia (alto acoplamiento) hacia LibraryRepository

Los cambios de LibraryRepository afectarán directamente a LibraryService, adicional a no poder reemplazar LibraryRepository  por otra clase sin afectar a LibraryService. Lo anterior se podría llamar alto acoplamiento entre componentes. 

Para reducir ese alto acoplamiento usamos DIP de la siguiente manera:


2.4 LibraryService ya no tiene una dependencia hacia LibraryRepository ahora depende de una abstracción y LibraryRepository depende también de dicha abstracción. 

2.5 LibraryService ahora trabaja con abstracciones (interfaces) en su código y no depende de ninguna implementación concreta.

Aunque a simple vista pareciera que no hemos hecho gran cosa, veamos cómo inicializamos la clase LibraryService. 

2.6 Inicialización de la clase LibraryService suministrando sus dos dependencias necesarias.

A diferencia de antes (cuando no teníamos DIP) donde solo podíamos pasar las dos implementaciones; ahora podremos colocar cualquier clase en nuestra inicialización siempre y cuando implemente la abstracción necesaria que tiene LibraryService.

Tenga en cuenta lo siguiente:

  1. La Inversión de dependencias necesita del patrón inyección de dependencias para funcionar. En otras palabras: No se puede tener Inversión de dependencia sin inyección de dependencia pero si es posible tener inyección de dependencia sin hacer inversión de dependencia.
  2. Aplicar Inversión de dependencia reduce el acoplamiento entre componentes.
  3. Aplicar SOLO inyección de dependencia sin hacer inversión NO reduce el acoplamiento.
  4. No siempre debemos tener inversión de dependencia en nuestro código, a veces es solo necesario tener inyección de dependencia. Busca aplicar Inversión en el momento que veas que vas a interactuar con otro módulo, si es una relación entre clases del mismo módulo bastará con hacer inyección.

Inversión de Control

La inversión de control es un principio de diseño de software en el cual se busca que el framework tome el control en el flujo del ciclo de vida de una petición.

Mirémoslo de esta forma: En un flujo normal cuando estás enfermo y necesitas de un doctor, tú vas al doctor para que te atienda. Si se invierte el control, en el momento que estás enfermo es el doctor quien viene a ti a atenderte.

La metáfora anterior, enfocada al ejemplo que estamos trabajando, busca conseguir que en lugar de inicializar instancias en nuestras clases e inyectar las dependencias manualmente sea el framework quien se encargue de dicha actividad, es decir, lo visto en la imagen 2.6 de crear un objeto y pasar las dependencias ya no será necesario; porque será el framework quien determine que tú necesitas una instancia de determinada clase y el mismo framework se encargará de suministrar las dependencias necesarias.

Para lograr lo anterior, la inversión de control trabaja con un contenedor donde almacenará todas las clases con las que puede manejar el ciclo de vida del objeto y sus dependencias asociadas.

Entonces podemos decir:

  1. La Inversión de control funciona gracias a la inyección de dependencia.
  2. La inversión de control no reduce en NADA el acoplamiento entre componentes, este solo se encarga de gestionar instancias y el ciclo de vida de los objetos.
  3. Se puede tener Inversión de control sin hacer Inversión de dependencia.
  4. Se puede hacer Inversión de control aplicando también inversión de dependencia, aplica la misma regla explicada en el punto 4 de inversión de dependencia. 

Autores: Daniel Saavedra, Richard Lion Guevara

7 comentarios en «Inyección de dependencias, Inversión de dependencias e inversión de control.»

  1. ¡Excelente artículo! Sin duda alguna el uso de estos principios de diseño de software facilitan enormente el trabajo, permitiéndonos realizar aplicaciones cada vez más escalables y que puedan adaptarse a las circunstancias del negocio.

    Es realmente doloroso cuando desde el negocio se plantea una funcionalidad donde se tiene que cambiar mucha parte del código actual para soportarla y al final esto traerá más problemas que beneficios.

    Adicionalmente, el saber en qué momento amerita el uso de estos principios y a su vez aplicarlos de la forma correcta, es lo que nos permite cada vez ser mejores arquitectos de software.

    Responder
  2. ¡Que buen articulo! explica muy bn estos 3 principios que a veces solemos confundir.

    Muy útiles también para entender y poder implementar bien patrones de diseño como Brigde, Template Pattern, entre otros.

    También es importante aclarar que fácilmente estos conceptos lo podríamos llevar a nuestra capa service (Por si hay reglas de negocio que requieran de varias implementaciones) y que sea una capa delegate quien se encargue de orquestar dichas interfaces de service

    Responder

Deja un comentario