Entendendo Injeção de Dependência com .NET
Published Jul 18 2022 05:00 AM 846 Views
Microsoft

Entendendo Injeção de Dependência com .NET

 

Um dos princípios da orientação a objetos é que nossas classes devem ter alta coesão e baixo acoplamento.

Isto quer dizer que nossas classes têm de fazer sentido, ter responsabilidade única, mas que elas serão utilizadas em conjunto com outras classes para atender ao objetivo de negócio.

Podemos afirmar então que uma classe pode tanto depender de outras classes para realizar seu trabalho como ela também pode ser a dependência de outras classes.

 

Dependência Forte e Dependência Fraca

 

Em alguns momentos podemos nos deparar com a afirmação de que uma classe tem uma dependência forte com outra classe.

Vejamos um exemplo:

public class CalculadoraFrete
{
    private readonly PrecoFreteRepository _repository;

    public CalculadoraFrete()
    {
        _repository = new PrecoFreteRepository();
    }

    public Task<decimal> CalcularFreteProduto(double peso, double distancia)
    {
        return _repository.SelecionarPrecoPorPesoEDistancia(peso, distancia);
    }
}

Podemos verificar que a classe CalculadoraFrete tem uma forte dependência da classe PrecoFreteRepository já que inicializamos o atributo _repository no construtor e utilizamos o mesmo no método CalcularFrete.

Quais os problemas com esta dependência forte?

  • Usuários externos da classe CalculadoraFrete não tem visibilidade desta dependência, e de quais dependências a classe PrecoFreteRepository necessita.
  • Não conseguimos substituir esta dependência por moqs ou stubs para, por exemplo, implementar um teste de unidade.

Para solucionarmos estes problemas podemos enfraquecer esta dependência transformando-a em um parâmetro que pode ser passado pelo chamador, como por exemplo:

public class CalculadoraFrete
{
    private readonly PrecoFreteRepository _repository;

    public CalculadoraFrete(PrecoFreteRepository repository)
    {
        _repository = repository;
    }

    public Task<decimal> CalcularFreteProduto(double peso, double distancia)
    {
        return _repository.SelecionarPrecoPorPesoEDistancia(peso, distancia);
    }
}

Podemos verificar então que agora para instanciarmos uma CalculadoraFrete precisamos passar uma instância de PrecoFreteRepository/ Isto torna esta dependência explicita para os consumidores, essa estratégia é conhecida como inversão de controle (IOC) mas o problema está parcialmente resolvido.

A menos que a classe PrecoFreteRepository declare o método SelecionarPrecoPorPesoEDistancia como abstrato ou virtual, não conseguimos passar uma implementação diferente.

Então para resolvermos este problema podemos extrair a interface do PrecoFreteRepository:

public interface IPrecoFreteRepository
{
    Task<decimal> CalcularFreteProduto(double peso, double distancia);
}

public class CalculadoraFrete
{
    private readonly IPrecoFreteRepository _repository;
    public CalculadoraFrete(IPrecoFreteRepository repository)
    {
        _repository = repository;
    }

    public Task<decimal> CalcularFreteProduto(double peso, double distancia)
    {
        return _repository.SelecionarPrecoPorPesoEDistancia(peso, distancia);
    }
}

Quando temos a dependência de interfaces ou classes abstratas tornamos ainda mais fácil a substituição por classes de testes ou com diferentes implementações para diferentes cenários.

 

Será que só com isto resolvemos o problema?

 

Ainda bem que você está prestando atenção no que estou falando.

Se analisarmos bem apenas mudamos onde a nossa dependência será inicializada. Antes dentro da nossa calculadora, agora por quem irá chamá-la.

No fim alguém ainda terá uma dependência forte e podemos associá-la a palavra reservada new. Então como minimizamos a quantidade de new em nosso código?

 

Finalmente a Injeção de Dependências

 

Como o próprio nome diz a Injeção de Dependências é um processo que usa o padrão de inversão de controle (IOC) citado acima para criar um mecanismo dinâmico e centralizado capaz de associar quais dependências cada classe possui. Essas relações ficam registradas em um Container de Injeção de Dependência (DI). Assim em tempo de execução recebemos instancias prontas com todas as dependências resolvidas.

O processo de injeção de dependência tem algumas vantagens:

  • Gestão do ciclo de vida. Em alguns momentos a simples inicialização dos objetos é onerosa e mantê-los vivos e reutilizá-los traz grandes ganhos de performance. Em outros casos precisamos de instâncias completamente novas. É excelente isolarmos este comportamento do nosso código.
  • Simplificação da inicialização: Em muitos casos, principalmente na inicialização de nossa aplicação temos um grafo de dependências muito extenso. Conhecer e implementar a inicialização pode ser muito trabalhoso. Quando terceirizamos este trabalho para a biblioteca temos uma maior estruturação de código e uma inicialização mais limpa.

Para os nossos exemplos aqui utilizaremos a nossa biblioteca, já conhecida para quem usa [ASP.NET Core]: Microsoft.Extensions.DependencyInjection.

Veremos hoje as duas principais classes, as quais interagimos em aplicativos ASP.NET Core, mas no contexto de uma aplicação console simples:

  • ServiceCollection: recebe o registro das dependências através de métodos de extensão como AddSingleton, AddScoped, AddTransient e muitos outros. Veremos o que são em postagens posteriores.
  • ServiceProvider: construído através do ServiceCollection nos permite receber uma instância de um serviço previamente registrado no ServiceCollection.

Então, em nosso cenário hipotético como seria o código que registra e inicializa nossa calculadora:

var services = new ServiceCollection();
services.AddSingleton<IPrecoFreteCalculadora, PrecoFreteCalculadora>();
services.AddSingleton<CalculadoraFrete>();

var provider = services.BuildProvider();

var calculadora = provider.GetRequiredService<CalculadoraFrete>();

Não é o momento ainda de entendermos todas as linhas, mas podemos verificar que:

  • Instanciamos um ServiceCollection e registramos duas dependências: IPrecoFreteRepository e CalculadoraFrete para que possamos utilizá-los posteriormente.
  • Com base em nosso ServiceCollection criamos nosso ServiceProvider que terá as dependências registradas.
  • Instanciamos a nossa CalculadoraFrete que será construída utilizando a instância de PrecoFreteRepository anteriormente registrado.

Novamente, para quem usa ASP.NET Core isto é transparente, mas no nosso próximo artigo veremos como potencializar qualquer de nossas aplicações com estas técnicas.

Deixe aqui nos comentários suas dúvidas, sugestões e o que você quer ver nos próximos artigos!

Até a próxima!

Co-Authors
Version history
Last update:
‎Jul 25 2022 06:04 AM
Updated by: