É 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 |
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.
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.

