Introducción a Inyección de Dependencias (DI)

viernes, 5 de septiembre de 2014

Logo de Inyección de Dependencias

La Inyección de Dependencias es un patrón de diseño que implementa uno de los principios SOLID, el Principio de Inversión de Dependencia, que dice que no se debería depender de concreciones sino de abstracciones.

Este es el primer post de una serie sobre inyección de dependencias (DI) e inversión de control (IOC), que normalmente se suelen mezclar y confundir, pero que son dos cosas totalmente diferentes.

De hecho en este primer post de la serie vamos a hacer una introducción a inyección de dependencias y veremos un ejemplo pero no vamos a utilizar, ni explicar, inversión de control. Vamos a realizar inyección de dependencias sin inversión de control, viendo así que son cosas diferentes.

Dependencia

Una dependencia directa es cuando un objeto depende directamente de otro y además se encarga de crearlo también.

public class ClassA
{

}

public class ClassB
{
    private ClassA objA;

    public ClassB()
    {
        objA = new ClassA();
    }
}

Problemas

La dependencias directas entre los componentes en nuestro código lo hacen muy acoplado y que sea menos escalable, esto quiere decir que para realizar cambios, sustituir componentes o ampliar funcionalidad hay que tocar mucha cantidad de código con el riesgo que eso supone. Y para corregir un error puede que estemos generando otro dentro de otra funcionalidad distinta al error original.

Cuando todo esta muy acoplado se hace difícil trabajar en equipo, se dan situaciones donde hasta que no termine mi compañero su parte, yo no puedo empezar. 

Es complejo poder realizar pruebas unitarias de un objeto porque para probarlo tengo que crearme su dependencia también y según que casos puede no ser muy cómodo.

Inyección de dependencia

Para que se produzca una inyección de dependencia se tienen que cumplir dos cosas:
  1. Un objeto no debe depender directamente de otro, sino de una abstracción.
  2. El objeto no crea su dependencia, alguien se la pasa o inyecta.
Para conseguir el punto número uno, yo siempre he utilizado una interfaz, supongo que podría ser válido con una clase base pero la verdad es que nunca lo he utilizado de esta manera.

Para conseguir el punto número dos, la forma más habitual es recibir el objeto que implementa la interfaz en el constructor, pero también hay más formas como por ejemplo a través de una propiedad.

public interface InterfaceA
{ 
}

public class ClassA:InterfaceA
{

}

public class ClassB
{
    private InterfaceA objA;

    public ClassB(InterfaceA objA)
    {
        this.objA = objA;
    }
}

Ventajas

Las ventajas que nos va a aportar esto es todo lo contrario a los problemas que teníamos con dependencias directas, es decir, nuestros objetos no están acoplados, si tengo una objeto B que depende de una abstracción de A, voy a poder sustituir la implementación de A sin tocar el objeto B.

Vamos a poder trabajar de forma más independiente entre compañeros, porque si yo me encargo de realizar el objeto B, no necesito que la implementación de A este codificado para poder trabajar. Me puedo crear un fake que implemente la interfaz y que me devuelva objetos escritos a mano directamente.

También nos va a facilitar mucho poder realizar pruebas unitarias utilizando mocks.

Ejemplo

Vamos a un ejemplo menos abstracto.

Imaginemos que tengo una aplicación que actualmente almacena los datos de producto en una base de datos SQL Server. Vamos a tener una entidad Product, una interfaz IProductRepository y un repositorio usando Entity Framework que se llama EFOrderRepository.

public class Product
{
    public string Id { get; set; }
    public string Title { get; set; }
}
public interface IProductRepository
{
    Product GetById(string id);
}

public class EFProductRepository
{
    public Product GetById(string id)
    {
        using (Context context = new Context())
        {
            return context.Products.FirstOrDefault(p => p.Id == id);
        }    
    }
}

Hay un servicio de aplicación como puede ser ProductService al que le inyectan el repositorio a través del constructor.

public class ProductService
{
    IProductRepository productRepository;

    public ProductService(IProductRepository repository)
    {
        this.productRepository = repository;
    }

    public Product GetById(string id)
    {
        if (string.IsNullOrEmpty(id))
            throw new ArgumentNullException();

        return productRepository.GetById(id);
    }
}

Bien pues con este escenario en un futuro podríamos sustituir el repositorio que usa Entity Framework por otro que utilice directamente ADO.Net, o algún micro orm, incluso que almacene los datos en alguna base de datos que sea NOSQL como MongoDB o Neo4j. 

Estos cambios no van a afectar al servicio porque este trabaja con una abstracción, que es la interfaz IProductRepository, no sabe donde se alojan los datos, ni le debe interesar, solo se debe preocupar por su propia lógica, que en este ejemplo sencillo es que si no le pasan un id lance una excepción, sino le pide los datos al repositorio y los devuelve.

Con la inyección de dependencias también se facilitan las pruebas unitarias. Si quisiéramos crear un test unitario para probar la lógica de la excepción, no tendríamos porque usar un repositorio real, sino que podríamos usar un mock y así no consultar una base de datos real. De esta forma se agilizan mucho los test unitarios y en un escenario donde pueden existir cientos puede ser muy interesante.

[TestMethod]
public void ShouldThrowArgumentNullExceptionTest()
{
    try
    {
        //creamos objetos dummy
        var mockRepository = new Mock<iproductrepository>();

        //ejecutamos el test
        ProductService service = new ProductService(mockRepository.Object);

        service.GetById(null);

        Assert.Fail("Not Throw ArgumentNullException");
    }
    catch (Exception ex)
    {
        //verificamos el test
        Assert.IsTrue(ex is ArgumentNullException);
    }
}

Resumen

Este este post hemos hecho una introducción a la inyección de dependencias y hemos visto las ventajas que nos puede aportar.

Hemos visto un ejemplo sencillo y como realizar un test unitario a una clase sin crearnos su dependencia real, solo pasándole un objeto dummy, suficiente para nuestro test. 

También hemos visto, pero de forma breve, que no tiene nada que ver con inversión de control o un contenedor de inversión de control, en futuros post tocaremos esta parte y espero que se entienda bien la diferencia.

Libros relacionados

Dependency Injection in .NET

No hay comentarios:

Publicar un comentario