Dependency Injection Design Pattern es un patrón de diseño ampliamente utilizado en el desarrollo de software para lograr un acoplamiento débil y mejorar la testabilidad, mantenibilidad y escalabilidad de las aplicaciones de software. En C#, la DI (Inyección de Dependencias) a menudo se implementa utilizando frameworks como .NET o bibliotecas de terceros (Unity, Ninject y otros).
Antes de profundizar, hablemos sobre el acoplamiento fuerte y el acoplamiento débil.
El acoplamiento fuerte es un escenario en el que los componentes dentro de un sistema dependen mucho entre sí, lo que dificulta cambiarlos de forma independiente. Si decides cambiar el objeto dependiente, también necesitarás cambiar las clases donde se utiliza este objeto dependiente.
El acoplamiento débil significa que los componentes dentro de un sistema son independientes entre sí. Eso significa que, si cambiamos un objeto, no afectará a ningún otro objeto que dependa de él.
Ejemplo:
Imagina una aplicación donde la clase Estudiante está fuertemente acoplada con una clase de base de datos específica. Si decides usar una base de datos diferente, la clase Estudiante necesitaría ser modificada directamente.
Imagina intentar cambiar permanentemente el color de tu piel; es desafiante porque está fuertemente acoplado a tu cuerpo. Ahora, imagina cambiar tu atuendo. Mucho más fácil, ¿verdad? Esa es la belleza del acoplamiento débil. Lee la analogía completa sobre el acoplamiento fuerte y el acoplamiento débil de BenKoshy.
Entonces, ¿cómo podemos desacoplar la clase Estudiante de la clase Base de Datos? Podemos introducir una interfaz para la Base de Datos y usar inyección de dependencias.
¿Qué es la Inyección de Dependencias?
La Inyección de Dependencias es un patrón que implica inyectar dependencias (servicios u objetos) en una clase que depende de los objetos o servicios (por ejemplo, la clase Estudiante mencionada anteriormente), en lugar de hacer que la clase (Estudiante) cree las dependencias por sí misma.
Una dependencia es un objeto en el cual otro objeto depende.
Este patrón nos permite crear un objeto de dependencia fuera de una clase dependiente y proporcionar el objeto de dependencia a la clase dependiente.
Dependency Injection Design Pattern:
Dependency Injection Design Pattern comprende tres clases esenciales:
1. Clase Cliente: La clase que depende de una clase de servicio, con la intención de utilizar sus métodos.
2. Clase de Servicio: La clase de servicio (dependencia) proporciona los servicios reales a la clase cliente.
3. Clase Inyectora: Una clase que inyecta el objeto de la Clase de Servicio en la Clase Cliente.
La clase inyectora instancia un objeto de la clase de servicio y lo inyecta en la clase cliente. La clase cliente utiliza el objeto de la clase de servicio inyectado para invocar sus métodos. Este enfoque ejemplifica cómo el Patrón de Diseño de Inyección de Dependencias separa la responsabilidad de crear un objeto de clase de servicio de la Clase Cliente.
Diferentes Tipos de Dependency Injection en C#:
La Inyección de Dependencias por Constructor implica inyectar dependencias a través del constructor de una clase.
La Inyección de Dependencias por Propiedad implica inyectar dependencias a través de propiedades de una clase.
Exploremos el patrón de diseño de DI, su concepto e implementación con fragmentos de código para mejorar tu comprensión. Primero, demostraremos el escenario de acoplamiento fuerte.
UserService:
UserRepository:
Creemos un escenario donde UserRepository necesita cambiar su implementación.
¿Qué crees que sucederá con la clase UserService?
No podemos hacer esta modificación simple sin afectar a UserService.
Otro ejemplo:
Supongamos un nuevo requisito (siempre surgirán) para una implementación diferente de UserRepository. En el escenario de acoplamiento fuerte, esto requeriría cambios en UserService.
Ahora, refactoricemos el código para demostrar un acoplamiento débil utilizando Inyección de Dependencias:
Introduciremos una interfaz IUserRepository. Cualquier nuevo repositorio introducido en nuestra aplicación debe implementar esta interfaz.
Refactored UserClass:
El UserService no sabe nada sobre la clase UserRepository, los cambios en esta clase no afectan a la clase UserRepository. Agreguemos un objeto de base de datos a la clase UserRepository y verifiquemos si esto afectaría a la clase UserService.
Añadiendo una base de datos al constructor de la clase UserRepository.
Cuando se agrega una base de datos al constructor de la clase UserRepository, la clase UserService no se ve afectada.
El UserService no está al tanto de este cambio, hemos logrado desacoplar con éxito la clase UserRepository de la clase UserService. Esto se debe a que UserService depende de la interfaz IUserRepository, lo que permite la inyección de diferentes implementaciones sin alterar el código del cliente.
Registrar Servicios en la Inyección de Dependencias: El Paso Final
El paso final implica registrar las implementaciones de servicios en el contenedor de Inyección de Dependencias. Este paso es crucial ya que permite que el contenedor proporcione instancias de servicios a las clases dependientes, siguiendo el principio de "Inversión de Control" (IoC).
Este registro implica informar al contenedor de Inyección de Dependencias sobre las implementaciones disponibles para una interfaz o servicio dado. El contenedor mantiene un registro de servicios y sus implementaciones correspondientes, lo que le permite satisfacer solicitudes de dependencia desde otras partes de la aplicación.
El Rol del Contenedor de Inyección de Dependencias
El contenedor de Inyección de Dependencias es responsable de gestionar el ciclo de vida y la resolución de dependencias. Durante el proceso de registro, el contenedor asocia interfaces con sus implementaciones concretas. Cuando una clase solicita una dependencia, el contenedor proporciona una instancia del servicio registrado.
En .NET, el registro de servicios se realiza típicamente en la clase Startup. Considera el siguiente ejemplo:
En este ejemplo, el método AddScoped indica que se debe crear una nueva instancia de UserRepository para cada solicitud de ámbito. Métodos similares como AddSingleton y AddTransient pueden utilizarse para diferentes escenarios de duración.
Si estás utilizando un contenedor de Inyección de Dependencias de terceros como Autofac o Unity, el proceso de registro puede diferir ligeramente.
Conclusion
En este artículo, exploramos el patrón de diseño de inyección de dependencias en C#. Comprender y aplicar este patrón es esencial para crear sistemas de software mantenibles, testeables y escalables.
Al adoptar la inyección de dependencias, puedes lograr una arquitectura más modular y flexible, lo que facilita el mantenimiento y la extensión de tu base de código.
I have used this 7 years back when I was working in asp.net mvc