Dependência
Uma dependência é quando uma classe depende de outra para realizar uma função, de forma a utilizar um recurso. Digamos que em uma aplicação que possui um componente de edição de textos, você quer adicionar a funcionalidade de verificação ortográfica, por exemplo:
No diagrama acima, o SpellChecker
é uma dependência de TextEditor
. A classe TextEditor está dependendo diretamente de SpellCHecker
, tendo a responsabilidade de instânciar e chamar a outra classe.
Isso permite a reutilização dessa funcionalidade por outras classes, porém gera um acoplamento forte entre as classes, sendo representado no código abaixo:
public class TextEditor {
private SpellChecker checker;
public TextEditor() {
this.checker = new SpellChecker();
}
}
Inversão de controle
Inversão de controle é uma técnica em que o controle é invertido na hierarquia de dependência entre as classes. Isso significa que uma classe dependente recebe suas dependências de uam classe externa, ao invés de cria-las internamente.
Orientação a interfaces
No primeiro exemplo, é gerado diversos problemas devido ao acoplamento a uma classe concreta, como:
- Para substituir a classe
SpellChecker
com uma implementação diferente, é preciso alterar a classe TextEditor; - Caso
SpellChecker
tenha outras dependências, elas precisam ser configuradas pela classeTextEditor
. Isso resulta em um código de configuração espalhado caso várias classes do sistema utilizem oSpellChecker
; - Não é possível criar mocks para testes unitários, por depender da implementação de
SpellChecker
.
Para resolver parte do acoplamento, o primeiro passo para reduzir o acoplamento é através do design orientado a interfaces. Com uma classe orientada a interfaces, é possível a alteração de uma implementação sem que afeta a classe que utilize essa dependência.
Um segundo passo é transferir a responsabilidade de criar os objetos das dependências para quem utiliza a classe, tendo ela então a responsabilidade de injetar a dependência, não mais o TextEditor
.
O diagrama acima mostra a utilização do TextEditor por uma outra classe (como a classe Main), tendo o controle sobre qual implentação da interface ISpellChecker utilizar, fornecendo-a antes de utilizar em si a TextEditor.
Adaptando o exemplo acima, a classe TextEditor
deve depender da interface de SpellChecker, e não mais da implementação. Além disso, ela deve dar a responsabilidade de injetar a implementação para outra classe, nesse exemplo é utilizado a injeção via construtor:
public class TextEditor {
private ISpellCHecker checker;
public TextEditor(ISpellCHecker checker) {
this.checker = checker;
}
}
Já o código para utilização da classe (caso não utilizar um framework para ajudar):
ISpellChecker sc = new SpellChecker(); // Implementação de ISpellChecker
TextEditor textEditor = new TextEditor(sc);
bool textoOk = textEditor.Check();
Injeção de dependências
Conforme um sistema vai crescendo, a criação de vários objetos pode se tornar um processo complexo e trabalhoso. Uma maneira é tornar necessário apenas com que o componente solicite a dependência, e não mais o fornecimento por um componente. Isso é possível através da injeção de dependência.
A injeção de dependência (ou dependency injection) é uma design pattern para sistemas com orientação a objetos, sendo uma especialização de inversão de controle tendo (IoC) o objetivo de injetar dinamicamente dependências em uma classe, em tempo de execução. Nesse caso, a responsabilidade de construir os objetos para um framework.
Tipos de injeção de dependência
Mesmo utilizando um framework para o fornecimento de dependências, é necessário com que a classe tenha uma maneira de receber essa implementação. O método utilizado servirá tanto com o uso do framework, quanto para uma injeção manual, sendo as 2 principais maneiras:
-
Injeção via construtor
É o exemplo utilizado acima, onde para construir uma instância de
TextEditor
, é obrigado fornecer uma implementação deISpellChecker
-
Injeção via Setter
Para esse caso, após instanciar a classe, a injeção é realizada via um método set, por exemplo:
public class TextEditor {
private ISpellChecker checker;
public TextEditor() {
}
public SetSpellChecker(ISpellChecker checker) {
this.checker = checker
}
}
Service Container do ASP.NET Core
O ASP.NET Core possui um container de serviços, onde é possível registrar as dependências. O .NET ficará responsável por criar e eliminar instâncias da dependência onde for necessário. É a partir dele que iremos aplicar a injeção de dependência.
ele torna mais fácil a configuração de implementações, ao invés de levar a responsabilidade de fornecer manualmente a implementação para uma classe, antes de conseguir utiliza-la, o que pode ser um processo demorado.
No container de serviços, é qual implementação de cada interface utilizar. Dessa forma, quando uma nova instância é requisitada, ele automaticamente resolve as dependências.
Ciclo de vida de uma dependência
Uma das principais vantagens da utilização de um container de IoC é o controle do ciclo de vida da instância da dependência, sem com que o desenvolvedor se preocupe em desenvolver essa lógica.
A escolha do ciclo de vida é realizada na etapa do registro da implementação, sendo os ciclos disponíveis no C#:
- Singleton - Uma instância compartilhada por todas as requisições;
- Scoped - Uma instância por requisição;
- Transient - Uma instância por chamada, sendo sempre diferentes.
Transferindo para código, cada forma ficaria da seguinte maneira:
services.AddSingleton<IMyDependency, Myependency>() // Singleton
services.AddScoped<IMyDependency, Myependency>() // Scoped
services.AddTransient<IMyDependency, Myependency>() // Transient
Existem casos específicos de quando usar cada uma das implementações. Por exemplo, Singleton
pode ser ideal para classes de cache
, por ser acessadas em todas as requisições. Essas diferenças e seus casos de uso serão discutidos no próximo artigo.
Conclusão
A inversão de controle é uma forma com que conseguimos injetar as dependências de uma classe através de uma classe externa, que junto do design orientado a interfaces, Ajuda na redução de acoplamento entre as classes.
Já a injeção de dependência, é um tipo de IoC no qual registramos o ciclo de vida e implementação de uma interface, transferindo a responsabilidade de criação de objetos para um framework, e eliminando a necessidade de criar manualmente cada objetos, o que pode se tornar complexo em sistemas grandes.
Caso tenha alguma informação incorreta, ou algum ponto a acrescentar, não deixe de comentar.
Fontes
https://stackoverflow.com/questions/3058/what-is-inversion-of-control
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0
https://stackoverflow.com/tags/dependency-injection/info
https://stackoverflow.com/questions/130794/what-is-dependency-injection
https://stackoverflow.com/questions/4596300/where-exactly-is-the-difference-between-ioc-and-di
https://learn.microsoft.com/en-us/previous-versions/msp-n-p/ff921087(v=pandp.10)
https://imasters.com.br/software/inversao-de-controle-service-locator-e-injecao-de-dependencia
Top comments (0)