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.
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?
CalculadoraFrete
não tem visibilidade desta dependência, e de quais dependências a classe PrecoFreteRepository
necessita.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.
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?
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:
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:
AddSingleton
, AddScoped
, AddTransient
e muitos outros. Veremos o que são em postagens posteriores.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:
ServiceCollection
e registramos duas dependências: IPrecoFreteRepository
e CalculadoraFrete
para que possamos utilizá-los posteriormente.ServiceCollection
criamos nosso ServiceProvider
que terá as dependências registradas.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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.