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:
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:
Observamos como la clase Library tiene un acoplamiento ‘leve’ debido a que la cantidad de clases de las que depende no son muchas.
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.
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:
- 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.
- 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.
Aunque a simple vista pareciera que no hemos hecho gran cosa, veamos cómo inicializamos la clase LibraryService.
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:
- 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.
- Aplicar Inversión de dependencia reduce el acoplamiento entre componentes.
- Aplicar SOLO inyección de dependencia sin hacer inversión NO reduce el acoplamiento.
- 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:
- La Inversión de control funciona gracias a la inyección de dependencia.
- 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.
- Se puede tener Inversión de control sin hacer Inversión de dependencia.
- 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
¡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.
Hola Stiven, gracias por tu comentario.
¡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
Excelente artículo.
Muchas gracias por el post Daniel! Bastante claro 🙂
Excelente contenido, super valioso. Muchas gracias.
Muy bueno el post, aclara mucho de estos conceptos que en general, aún cuando los estamos aplicando diariamente, no están claros.