Cómo funciona la inyección de dependencia de.Net Core

Cómo funciona la inyección de dependencia de.Net Core

Uno de los aspectos más importantes a considerar al desarrollar software es cómo administrará las dependencias. Una dependencia es un componente externo en el que se basa una pieza de software para funcionar correctamente. Por ejemplo, una aplicación web.NET puede depender de un servidor de base de datos, una cola de mensajes o un Interfaz de programación de aplicaciones (API) de terceros.

Un sistema de software puede resultar complicado de mantener y escalar sin una gestión adecuada de las dependencias. Si no administra las dependencias correctamente, pueden acoplarse estrechamente al código que las usa. Este acoplamiento puede dificultar el cambio o la actualización de una dependencia sin romper el código.

Ahí es donde entra la inyección de dependencia (DI).

Índice
  1. ¿Qué es la inyección de dependencia (DI)?
  2. Principio de inversión de dependencia en inyección de dependencia
  3. Por qué usar inyección de dependencia
  4. .NET Core y la inyección de dependencia
  5. El papel de los contenedores IoC en la implementación
  6. Creación de un contenedor IoC propio
  7. Tiempos de vida de servicio para una dependencia
  8. Otros enfoques comunes para la inyección de dependencia
  9. Aprenda a usar la inyección de dependencia en.NET Core

¿Qué es la inyección de dependencia (DI)?

DI es una técnica de desarrollo de software para lograr dependencias de código débilmente acopladas entre los objetos y sus colaboradores, o "dependencias". Al usar DI, la responsabilidad de ubicar y proporcionar dependencias se elimina de un objeto y se entrega a un tercero. Hacerlo da como resultado mayor flexibilidad y mantenibilidad en el desarrollo de software.

En.NET Core, un contenedor DI integrado administra las dependencias entre objetos. El contenedor DI implementa el principio de inversión de control (IoC), que establece que un objeto no debe ser responsable de ubicar o administrar sus dependencias. En cambio, esta responsabilidad se invierte y se da a un tercero.

El uso de contenedores DI puede resultar en un código más modular y comprobable. Además, debido a que el contenedor gestiona las dependencias entre objetos, puede proporcionar una mayor flexibilidad en la forma en que se crean e inyectan las dependencias en los objetos.

Además del contenedor DI integrado, hay muchos otros contenedores IoC de terceros disponibles para.NET Core. Estos contenedores pueden proporcionar características adicionales y opciones de configuración.

La aparición de problemas de dependencia

Los problemas de dependencia pueden ocurrir en cualquier base de código, pero son especialmente frecuentes en el código estrechamente acoplado. Cuando el código está estrechamente acoplado, las dependencias entre los objetos son muy fuertes.

Este estrecho acoplamiento puede dificultar el cambio o la actualización de un objeto sin afectar a otros objetos del sistema.

Un ejemplo de un problema de dependencia

Considere una aplicación web hipotética de.NET que utiliza una API de terceros para obtener datos. Supongamos que el código de esta aplicación está estrechamente relacionado con la API. Como resultado, si la API cambia, el código también deberá actualizarse.

Si el desarrollador actualiza la API con frecuencia, esto puede causar problemas importantes, ya que también requiere cambios constantes en el código de la aplicación web your.Net.

Esencialmente, la necesidad de cambiar su código dependerá de si la API cambia, lo cual está fuera de su control ya que lo desarrolla un tercero.

Principio de inversión de dependencia en inyección de dependencia

El Principio de inversión de dependencia (DIP) es un principio de diseño de software que establece que las dependencias deben invertirse y que un objeto no debe ser responsable de ubicar o administrar sus dependencias. En cambio, esta responsabilidad debería invertirse y atribuirse a un tercero. Dos conceptos ayudan a lograr esta inversión de control:

Los módulos de alto nivel deberían depender de abstracciones

El primer concepto del DIP es que los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Un módulo es de alto nivel si es responsable de una parte importante de la funcionalidad de la aplicación. Por el contrario, un módulo de bajo nivel es cualquier módulo que no sea de alto nivel, como una clase de utilidad.

Tanto los módulos de alto nivel como los de bajo nivel deben depender de abstracciones. Una abstracción es un supertipo que define el contrato para un grupo de tipos. En otras palabras, es una interfaz o clase base que define qué funcionalidad debe estar presente para que un tipo sea considerado miembro del grupo.

Al depender de abstracciones, los módulos de alto nivel no están vinculados a ninguna implementación particular de los módulos de bajo nivel. Esto facilita cambiar la implementación de los módulos de bajo nivel sin afectar los módulos de alto nivel.

Las abstracciones no deberían depender de los detalles

El DIP establece que las abstracciones no deben depender de los detalles. Un detalle es cualquier implementación concreta de una abstracción. Por ejemplo, si una abstracción es una interfaz, un detalle sería una clase que implementa esa interfaz. En cambio, los detalles deben basarse en abstracciones.

Este principio está estrechamente relacionado con el anterior, ya que garantiza que los módulos de alto nivel no dependan de los módulos de bajo nivel. Sin embargo, lleva las cosas un paso más allá al afirmar que incluso las abstracciones no deberían depender de detalles concretos. Al depender de abstracciones en lugar de detalles, puede hacer que su código sea más flexible y extensible.

Dependiendo de los detalles, los módulos de alto nivel quedan ligados a la implementación particular de los módulos de bajo nivel. Como resultado, dificulta cambiar la implementación de los módulos de bajo nivel sin afectar los módulos de alto nivel.

Al seguir el Principio de Inversión de Dependencias, las dependencias se invierten y son administradas por un tercero, lo que facilita mucho cambiar la implementación de las dependencias sin afectar el código que depende de ellas. Además, permite realizar pruebas unitarias de los módulos de forma aislada de sus dependencias.

Puedes leer:  Expectativas realistas para el tiempo de desarrollo de software

Por qué usar inyección de dependencia

Debe considerar seriamente el uso de una DI para administrar las dependencias al desarrollar software. Las siguientes son las principales razones por las que debe hacer esto:

Código Flexible

Cuando se inyectan dependencias en su código, el código se vuelve más flexible. Esta flexibilidad es el resultado de poder intercambiar fácilmente las dependencias con diferentes implementaciones.

Por ejemplo, si usa un contenedor DI, puede cambiar la configuración para usar una implementación diferente de una dependencia. Hacerlo es mucho más fácil que cambiar el código en sí.

Facilidad en las pruebas

Otra razón para usar DI es que hace que las pruebas unitarias sean mucho más manejables porque las dependencias se pueden simular o bloquear fácilmente, lo que se refiere a proporcionar implementaciones falsas de las dependencias.

Como resultado, puede probar el código de forma aislada de las dependencias. Hacerlo es imposible si el código depende directamente de la implementación real de las dependencias.

Más fácil de mantener

Además, el código que usa DI generalmente es más fácil de mantener porque las dependencias están claramente definidas y se pueden cambiar fácilmente si es necesario. También es menos probable que el código se rompa si cambia una dependencia, ya que el código no usa directamente las dependencias. Como resultado, pasará menos tiempo reparando el código roto.

Más legible

El código que usa DI es más legible porque las dependencias están claramente definidas y se pueden entender fácilmente. Es más fácil de entender porque las dependencias se inyectan en el código, en lugar de definirse dentro del código, lo que deja más claro para qué sirve cada dependencia.

Además, es menos probable que el código esté lleno de código innecesario.

Estructura de clases más extensible

Cuando se usa DI, la estructura de clases es más extensible. La estructura de clases se refiere a la organización de clases en el código. Por ejemplo, si una clase depende de otra clase, se dice que la clase dependiente es una "clase secundaria".

Al usar DI, puede inyectar las dependencias en las clases secundarias, haciéndolas más extensibles. Al ser más extensible, es más fácil agregar nuevas funciones a las clases secundarias. Hacerlo es imposible si las dependencias están definidas dentro de las clases secundarias.

Facilita el desarrollo del equipo

DI también puede facilitar el desarrollo del equipo, ya que cada miembro del equipo puede trabajar en un módulo diferente de forma aislada. Como resultado, es menos probable que los miembros del equipo se interpongan entre sí. Además, es más fácil integrar el trabajo de diferentes miembros del equipo ya que las dependencias están claramente definidas.

.NET Core y la inyección de dependencia

.NET Core tiene un contenedor IoC integrado que puede usar para DI. Este contenedor se basa en la biblioteca del Localizador de servicios comunes de Microsoft. La biblioteca Common Service Locator es una biblioteca liviana que proporciona una forma coherente de ubicar servicios.

Puede usar el contenedor IoC en.NET Core para resolver dependencias de varias maneras. Por ejemplo, el contenedor puede resolver dependencias por tipo, nombre o convención. Además, el contenedor puede resolver las dependencias de forma perezosa, lo que significa que no se creará una instancia de la dependencia hasta que se necesite.

El papel de los contenedores IoC en la implementación

IoC es un principio de programación que establece que una clase no debe depender de otra clase, lo que permite que se acoplen libremente. En su lugar, la dependencia debe inyectarse en la clase. IoC también se conoce como DI.

Los contenedores IoC se utilizan para implementar IoC. Un contenedor es simplemente una clase que almacena objetos y gestiona sus ciclos de vida. El contenedor es responsable de crear objetos, inyectar dependencias y eliminar objetos cuando ya no se necesitan.

Registro

Para usar un contenedor IoC, primero debe registrar sus tipos con el contenedor, lo que significa que debe especificar qué tipos debe instanciar el contenedor. Puede registrar tipos especificando el tipo de objeto que se creará, el nombre del objeto o la convención que se utilizará. Hay dos formas de registrar tipos con el contenedor.

La primera forma es usar atributos. Los atributos decoran sus tipos con metadatos. El contenedor usa estos metadatos para determinar cómo instanciar y resolver dependencias para sus tipos.

La segunda forma de registrar tipos con el contenedor es usar una API fluida. Puede usar API fluidas para configurar el contenedor mediante una interfaz fluida, que se realiza en el código o en la configuración.

Resolución

Una vez que registre sus tipos con el contenedor, puede usarlo para resolver dependencias llamando a los métodos "GetService" o "GetInstance" en el contenedor. Estos métodos devuelven una instancia del tipo solicitado. El contenedor es responsable de crear el objeto e inyectar las dependencias requeridas.

Disposición

El contenedor puede disponer de un objeto cuando ya no se necesita. Puede hacerlo llamando al método "Release" en el contenedor, lo que hace que el contenedor llame al método "Dispose" en el objeto, si implementa IDisposable.

IDisposable es una interfaz que define un método de eliminación. Al hacer esto, puede asegurarse de que sus objetos se eliminen correctamente y que los recursos no administrados que estén utilizando se liberen correctamente.

Puedes leer:  Tablas de Organización: Mejora la Eficiencia en el Almacenamiento de Datos

Creación de un contenedor IoC propio

Si desea crear su propio contenedor IoC, debe comenzar introduciendo las dependencias que administrará el contenedor. Estas dependencias se denominan servicios. Una vez que introduzca los servicios, puede comenzar a escribir el código para su contenedor. Los siguientes son los dos tipos de servicios que puede introducir:

Servicios marco

El primer tipo de servicio son los servicios marco. Estas son dependencias que requiere el propio framework. por ejemplo, el Marco ASP.NET Core requiere un servicio para resolver dependencias por tipo. Este servicio se llama "TypeActivatorCache".

Servicios de aplicaciones

El segundo tipo de servicio son los servicios de aplicación. Estas son dependencias que requiere su aplicación.

Por ejemplo, su aplicación puede requerir un servicio para resolver dependencias por nombre. Este servicio se llama "NameResolver". Otro ejemplo de un servicio de aplicación es el "DataAccessor". Este servicio proporciona una forma de acceder a los datos de una base de datos.

Tiempos de vida de servicio para una dependencia

Cada dependencia registrada puede tener una duración diferente. El tiempo de vida se refiere a la cantidad de tiempo que el contenedor mantendrá una referencia al objeto. Una vez que expire la vida útil, el contenedor se deshará del objeto.

Puede especificar tres tiempos de vida diferentes: Transitorio, Singleton y Alcance. Elegir qué tiempo de vida usar dependerá de las necesidades de su aplicación. Por ejemplo, si su aplicación solo necesita una instancia de un servicio, puede usar la duración de Singleton. Si lo hace, el contenedor creará solo una instancia del servicio y la mantendrá durante el tiempo de vida de la aplicación.

Con eso en mente, las siguientes son las tres vidas diferentes que puede especificar para una dependencia:

Transitorio

La duración transitoria hará que el contenedor cree una nueva instancia del servicio cada vez que se resuelva. Como resultado, cada vez que llame a GetService o GetInstance, se creará un nuevo objeto.

Por ejemplo, si su aplicación tiene un servicio que accede a los datos de una base de datos, es posible que desee utilizar la duración transitoria. Si lo hace, se asegurará de que cada vez que se utilice el servicio, obtendrá los datos más recientes de la base de datos. El tiempo de vida transitorio es el tiempo de vida predeterminado si no se especifica uno.

único

El tiempo de vida de Singleton hará que el contenedor cree una única instancia del servicio y la reutilice para cada resolución.

Como resultado, se devolverá el mismo objeto cada vez que llame a GetService o GetInstance. Por ejemplo, si su aplicación tiene un servicio que almacena datos de usuario, como preferencias, es posible que desee utilizar la duración de Singleton. Si lo hace, se asegurará de que todo el código use la misma instancia del servicio y, por lo tanto, los mismos datos.

Alcance

La duración del alcance hará que el contenedor cree una nueva instancia del servicio para cada solicitud. Supongamos que su aplicación recibe dos solicitudes simultáneamente. A continuación, creará dos instancias del servicio. Sin embargo, si su aplicación recibe dos solicitudes en rápida sucesión, solo creará una instancia del servicio.

Por ejemplo, si su aplicación tiene un servicio que accede a los datos de una base de datos, es posible que desee utilizar la duración del alcance. Si lo hace, se asegurará de que cada solicitud obtenga su propia instancia del servicio. Sin embargo, si dos solicitudes se suceden rápidamente, solo se creará una instancia del servicio, lo que ayudará a mejorar el rendimiento mediante la reutilización de los servicios.

Otros enfoques comunes para la inyección de dependencia

Hay varios otros enfoques de DI que vale la pena mencionar. Puede utilizar estos enfoques además de los mencionados anteriormente o en lugar de ellos. Teniendo eso en cuenta, los siguientes son otros tres enfoques comunes para DI:

Inyección de constructor

En la programación orientada a objetos, un constructor es un tipo especial de subrutina llamada para crear un objeto. La inyección de constructor es una DI donde las dependencias se inyectan en el constructor de la clase, que luego es responsable de instanciar el objeto.

Este enfoque se usa a menudo junto con los servicios de por vida Singleton y Scoped, ya que es cuando se llama al constructor.

Inyección de método

Un método es una función asociada a una clase. Un método define el comportamiento de un objeto. Cuando se llama a un método, se ejecuta en un objeto. Inyección de método es una forma de DI donde las dependencias se inyectan en el método de la clase, que luego es responsable de instanciar el objeto.

Inyección de propiedad

Una propiedad es un valor que está asociado con una clase. La inyección de propiedades es una forma de DI donde las dependencias se inyectan en las propiedades de la clase, que luego es responsable de instanciar el objeto. Esto puede verse como una forma más "liviana" de DI porque no requiere que la clase tenga un constructor o método.

Aprenda a usar la inyección de dependencia en.NET Core

DI es una técnica poderosa que puede ayudarlo a desacoplar el código en sus aplicaciones. Como resultado, puede hacer que su código sea más mantenible y más fácil de probar. Además, puede mejorar el rendimiento de sus aplicaciones mediante la reutilización de servicios.

.NET Core proporciona un buen soporte para DI y muchos contenedores DI están disponibles. Como tal, .Net Core es una excelente opción a la hora de elegir una plataforma para tus aplicaciones.


Si quieres conocer otros artículos parecidos a Cómo funciona la inyección de dependencia de.Net Core puedes visitar la categoría Desarrollo.

Entradas Relacionadas 👇👇

Go up