Design de software

Injeção de dependência (DI): tirar o new da classe

É comum criar os objetos necessários diretamente no ponto em que serão usados no código. Mas, quando você tenta escrever testes, bate em um muro. O código de criação fica preso dentro da classe, e não dá para substituí-lo.

5 min de leitura
Imagem de capa de Injeção de dependência (DI): tirar o new da classe

É comum criar os objetos necessários diretamente no ponto em que serão usados no código. Mas, quando você tenta escrever testes, bate em um muro. O código de criação fica preso dentro da classe, e não dá para substituí-lo.

Mesmo ao pesquisar o que é injeção de dependência (DI), aparecem termos como “inversão de controle” e “contêiner IoC”, o que pode deixar tudo ainda mais confuso.

Por isso, hoje vou explicar da forma mais simples possível. Vamos começar pela conclusão.

Injeção de dependência (DI) é criar fora da classe o objeto que antes era criado dentro dela e passá-lo para a classe. Esse único deslocamento é o ponto central: tirar a criação do objeto de dentro da classe. Isso é praticamente tudo o que a DI envolve.

Ao terminar este artigo, você entenderá por que a DI é necessária, como o código muda e por que bibliotecas como Swinject fazem isso por você.

Qual é o problema de criar o objeto dentro da classe?

Imagine que exista uma classe responsável por processar pedidos. Ela precisa de um módulo de pagamento para realizar os pagamentos.

O código iniciante mais comum fica assim.

class OrderService {
    // Criar o objeto de pagamento diretamente dentro da classe
    private let pay = KakaoPay()

    func order() { pay.pay() }
}

O problema é que OrderService fica fortemente acoplado ao KakaoPay.

E se você quiser trocar KakaoPay por NaverPay? Será preciso entrar na classe e alterar o trecho de criação.

Mesmo que você queira inserir um objeto de pagamento falso durante os testes, não há como. A classe já criou um KakaoPay real internamente.

Ou seja, deixar a criação do objeto dentro da classe prende a classe a uma implementação específica.


Por isso, tiramos a criação do objeto de dentro

A solução é mais simples do que parece. Não crie diretamente; crie fora e apenas receba.

class OrderService {
    private let pay: Pay
    // Apenas receber o que foi criado e passado de fora
    init(pay: Pay) { self.pay = pay }

    func order() { pay.pay() }
}

Só uma coisa mudou: a criação direta desapareceu, e o objeto passou a ser recebido pelo inicializador.

Agora, OrderService não precisa saber se o pagamento é KakaoPay ou NaverPay. Ele só sabe que recebe e usa algo chamado Pay.

A essência da DI não tem nada de grandiosa. Ela apenas move a criação do objeto para fora da classe e transfere para fora a decisão sobre o que será passado.

Passar um objeto de fora para dentro é chamado de “injetar”. Você injeta o objeto do qual a classe depende; por isso, injeção de dependência.

O nome pode parecer difícil, mas é só isso que ela faz.


Quais são as vantagens de usar DI?

Dizer apenas que é bom não ajuda muito, então resumi o que realmente muda.

  • A troca fica fácil: mesmo substituindo KakaoPay por NaverPay, você não altera uma linha sequer de OrderService. Basta trocar o objeto fornecido.
  • Os testes ficam mais fáceis: você pode inserir um objeto de pagamento falso (Mock) em vez do real e validar apenas a lógica, sem movimentar dinheiro.
  • As responsabilidades ficam separadas: OrderService se concentra apenas em “processar pedidos”, enquanto o código externo decide “qual pagamento usar”.

O terceiro ponto foi o que mais fez sentido para mim. Quando a classe se concentra apenas no próprio trabalho, o código fica muito mais fácil de ler.

Em resumo:

Categoria Criação dentro Criação fora (DI)
Troca do pagamento É preciso modificar a classe Troca-se apenas o objeto fornecido
Testes Não é possível inserir um objeto falso É possível injetar um Mock
Responsabilidades Criação e lógica misturadas Foco apenas na lógica
Depois de tirar o new de dentro, o código fica assim de organizado
Depois de tirar o new de dentro, o código fica assim de organizado

Então, por que precisamos de bibliotecas de DI como o Swinject?

Nesse ponto, surge uma dúvida natural: “Entendi que algo é passado de fora, mas quem gerencia esse ‘fora’?”

Se houver poucos objetos, você pode criá-los e passá-los manualmente. Mas projetos reais têm centenas de objetos interligados.

Fazer uma pessoa criar e passar tudo manualmente, na ordem correta, é um inferno.

Por isso surgiram ferramentas que fazem esse “criar e passar objetos de fora”. Contêineres de DI do ecossistema Swift, como Swinject e Factory, têm exatamente esse papel.

O contêiner cria os objetos e os encaixa onde são necessários
O contêiner cria os objetos e os encaixa onde são necessários
Quando há centenas de objetos, você agradece por ter um contêiner para gerenciá-los
Quando há centenas de objetos, você agradece por ter um contêiner para gerenciá-los

Registre os objetos necessários, e o contêiner os criará e os inserirá onde for preciso. Você quase nunca precisará criá-los diretamente.

É daí que também vem a conhecida “inversão de controle (IoC)”. Isso significa que o controle de criar e conectar objetos passou do seu código para o contêiner.

Em outras palavras, DI é o conceito, e o contêiner de DI é o trabalhador que o executa por você.


Perguntas frequentes

P. DI e IoC são a mesma coisa? Não. IoC (inversão de controle) é o princípio amplo de transferir o controle, enquanto DI é uma forma concreta de implementá-lo. DI é um tipo de IoC.

P. Existem formas de injeção além do construtor? Sim. Existem injeção pelo construtor, injeção por propriedade e injeção por método. No Swift, porém, recomenda-se a injeção pelo construtor. As dependências podem ser fixadas com let, não mudam e os testes ficam mais fáceis.

P. É obrigatório usar uma biblioteca para ser DI? Não. Passar manualmente um objeto para o construtor, como acabamos de fazer, também é DI. As bibliotecas apenas automatizam isso.


Hoje, explicamos a injeção de dependência como uma pequena mudança: alterar o local onde um objeto é criado.

Se DI parece difícil, lembre-se de uma coisa: “Não crie dentro da classe; crie fora e passe para dentro.” O restante vem naturalmente.

Se você já se sentiu perdido diante do código de testes, recomendo alterar manualmente o exemplo de hoje. A ideia ficará muito mais clara do que apenas olhando.

Continue lendo