C# - Inversion of Control e Dependency Injection


 Hoje vamos revisitar os conceitos de IoC e Dependency Injection.

A Dependency Injection, também conhecido como DI, é um Design Pattern usado para evitar o acoplamento em seu código.

Uma dependência é um objeto que sua classe precisa para fazer o trabalho, ou seja, este objeto não é criado dentro de sua classe. A dependência é injetada em uma classe que a usa, portanto, a dependência deve ser externa. A instanciação do objeto (ou seja, a dependência) não deve estar dentro da classe, mas fora dela.

Veja o exemplo a seguir:

    [Route("api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        private readonly NorthwindContext _context;
        public OrdersController(NorthwindContext context)
        {
            _context = context;
        }
     ...
     }

Temos neste código o exemplo clássico da injeção da dependência via construtor da classe.

Neste código, ao instanciar a classe OrdersController, uma instância do tipo NorthwindContext será injetada no construtor através do parâmetro context que vai atribuir o seu valor para a variável _context que poderá ser usada nos demais métodos da classe.

Mas quem vai fazer o trabalho de injetar a instância ? (Alguém tem que fazer o trabalho pesado!!!)

Neste exemplo será o contêiner DI da ASP .NET Core que vai injetar a instância da classe/serviço. Existem outros contêineres DI que podemos usar como : Unity, Castle Windsor, StructureMap, AutoFac,  Spring.NET, etc.

É um detalhe bem simples mas usar a injeção de dependência de traz os seguintes benefícios:

A injeção via construtor talvez seja a forma mais usada de aplicar este recurso e tem as seguintes vantagens:

No entanto existem mais três formas de realizar a injeção de dependência que veremos a seguir:

1-  Injeção de dependência usando propriedades

A implementação da injeção de dependência via propriedades - Setter Injection - não força a dependência ser passada para o construtor. Ao invés disso, as dependências são definidas em propriedades públicas expostas pelo objeto.

Esta abordagem tem as seguintes motivações:

Esta abordagem deve ser usada com cautela pois não se têm uma clara idéia de quais dependências são necessárias e se ocorrer alguma exceção é difícil rastrear.

Neste código estamos injetando a dependência através da propriedade pública da classe Cliente

A desvantagem desta abordagem é que os objetos são expostos publicamente e isso quebra a regra de encapsulamento da programação orientada a objeto.

2 - Injeção de dependência via interface

A implementação da injeção de dependência via interface -  Interface Injection -  utiliza uma interface comum que outras classes necessitam implementar para injetar a dependência.

Para este exemplo vou usar o seguinte cenário: Considere um ambiente com duas camadas :

interface IBLL
 { }

 class ProdutoBLL : IBLL
 {}

 class ClienteBLL : IBLL
 {}

 class CamadaUI : IUI
 {
   private IBLL camadaNegocios;
   public void SetObjectBLL (IBLL camadaNegocios);
   {
     this.camadaNegocios = camadaNegocios;
   }
 }

No código acima temos que o método SetObjectBLL da classe CamadaUI aceita um parâmetro do tipo IBLL.

A seguir temos exemplos de como podemos chamar o método SetObjectBLL para injetar a dependência para qualquer tipo de classe IBLL:

BLL camadaNegocios = new ProductoBLL();
CamdaUI camadaUI = new CamadaUI();
camdaUI.SetObjectBLL(camadaNegocios);         

ou

IBLL camadaNegocios = new ClienteBLL();
CamdaUI camadaUI = new CamadaUI();
camdaUI.SetObjectBLL(camadaNegocios);
 

Nesta implementação usando interfaces estamos passando uma referência para um tipo IBLL ao invés de uma instância do tipo.

3- Service Locator

A injeção de dependência via Service Locator usa um registro central conhecido como "localizador de serviço" que, mediante solicitação, retorna as informações necessárias para executar uma determinada tarefa.

A seguir temos uma implementação bem simples de Service Locator:

public interface IServiceLocator
{
    T GetService<T>();
}

A seguir a implementação do contrato :

class ServiceLocator : IServiceLocator
{
	private IDictionary<object, object> services;

	internal ServiceLocator()
	{
		services = new Dictionary<object, object>();

		this.services.Add(typeof(IServiceA), new ServiceA());
		this.services.Add(typeof(IServiceB), new ServiceB());
		this.services.Add(typeof(IServiceC), new ServiceC());
	}

	public T GetService<T>()
	{
		try
		{
		     return (T)services[typeof(T)];
		}
		catch (KeyNotFoundException)
		{
		     throw new ApplicationException("Serviço solicitado não esta registrado");
		}
	}
}

Neste código o construtor da classe registra todos os serviços disponíveis em um dicionário. Em nosso exemplo, temos 3 serviços diferentes acessíveis por meio de IServiceA, IServiceB e IServiceC. (Presume-se aqui que ServiceA implementa IServiceA e assim por diante.)

O método GetService() retorna uma referência da implementação correta buscando-a no dicionário

É assim que um cliente chamaria o serviço:

 static void Main(string[] args)
{
    IServiceLocator locator = new ServiceLocator();
    IServiceA myServiceA = locator.GetService<IServiceA>();
}

Os clientes não conhecem as classes reais que implementam o serviço. Eles só precisam interagir com o localizador de serviço para chegar a uma implementação.

Ele é considerado um antipadrão sendo usado para  encapsular os processos envolvidos na obtenção de um serviço com uma camada de abstração.

"Não estejais inquietos por coisa alguma; antes as vossas petições sejam em tudo conhecidas diante de Deus pela oração e súplica, com ação de graças."
Filipenses 4:6

Referências:


José Carlos Macoratti