Design de software

DI, IoC e DIP: entenda de vez a diferença entre os três termos

DI, IoC e DIP são conceitos de níveis diferentes. DIP é o princípio que define do que depender; IoC trata de quem detém o controle; e DI é a técnica de fornecer dependências. Este artigo organiza as diferenças com exemplos e respostas para entrevistas.

5 min de leitura
Imagem de capa de DI, IoC e DIP: entenda de vez a diferença entre os três termos

Quem estuda desenvolvimento provavelmente já ficou confuso pelo menos uma vez com estes três termos: DI, IoC e DIP.

Quando cursos e artigos dizem algo como “um contêiner IoC injeta dependências por meio de DI para seguir o DIP”, fica fácil perder a noção do que cada termo significa.

Vamos começar pela conclusão: os três termos estão em níveis diferentes. DIP é o princípio — por que fazer isso. IoC é a direção geral que realiza esse princípio — quem tem o controle. DI é a técnica concreta que implementa essa direção — como fornecer a dependência.

Depois de ler este artigo, você vai entender como os três conceitos se relacionam e poderá responder sem hesitar a perguntas de entrevista sobre eles. Também mantive os exemplos de código o mais curtos possível.

Os três termos resumidos em uma linha

É muito mais fácil começar memorizando uma linha para cada termo.

  • DIP (Dependency Inversion Principle, princípio da inversão de dependência): princípio de design que recomenda depender de abstrações, como protocolos, em vez de implementações concretas.
  • IoC (Inversion of Control, inversão de controle): uma estrutura em que o framework ou contêiner controla o fluxo do programa, e não o seu código.
  • DI (Dependency Injection, injeção de dependência): técnica que fornece os objetos necessários externamente, em vez de criá-los internamente.

A relação entre os três é esta.

DIP é o objetivo e o princípio; IoC é o conceito mais amplo para alcançar esse objetivo; e DI é uma das formas concretas de implementar IoC.

Em outras palavras, o nível de abstração diminui nesta ordem: DIP > IoC > DI.

Diagrama hierárquico que parte de DIP, passa por IoC e se divide em DI, callbacks e métodos template
Os níveis ocupados pelos três termos, do princípio à técnica

DIP trata de “do que depender”

DIP é o D de SOLID, o princípio da inversão de dependência entre os cinco princípios fundamentais de design orientado a objetos.

A ideia central tem duas partes: módulos de alto nível não devem depender de módulos de baixo nível, e ambos devem depender de abstrações.

Parece complicado, mas fica bem mais simples no código.

Veja um exemplo ruim em que um serviço de pedidos está diretamente ligado a um provedor de pagamento específico, o Kakao Pay.

// Exemplo ruim: dependência direta de uma classe concreta
class OrderService {
    private let pay = KakaoPay() // Para trocar o provedor de pagamento, é preciso desmontar esta parte
}

Para mudar depois para o Naver Pay, você teria que alterar diretamente o código do OrderService. Cada novo meio de pagamento abalaria o módulo de alto nível.

O DIP inverte essa situação. Você define uma abstração para pagamento, um protocolo, e depende apenas dela.

protocol PayGateway { func pay(amount: Int) }

class OrderService {
    private let pay: PayGateway // Dependa de uma abstração, não de uma classe concreta
    init(pay: PayGateway) { self.pay = pay }
}

Seja Kakao Pay ou Naver Pay, basta implementar PayGateway; o OrderService não precisa ser alterado. É isso que significa inverter a direção da dependência.


IoC trata de “quem tem o controle”

IoC, ou inversão de controle, é um conceito um pouco mais amplo.

Normalmente, o código que escrevemos controla todo o fluxo: criamos objetos, chamamos métodos e definimos a ordem de execução.

IoC inverte esse controle. Em vez de nós chamarmos o framework, o framework nos chama.

Também é conhecido como “Princípio de Hollywood”: “Não nos chame, nós ligaremos para você (Don’t call us, we’ll call you).”

Com um contêiner de DI como o Swinject, o contêiner cria, gerencia e conecta os objetos em vez de você criá-los diretamente. O controle passa de você para o contêiner.

Há um ponto importante aqui: IoC não se limita a DI.

  • Um framework chamando um callback
  • O padrão Template Method
  • O UIKit chamando métodos de ciclo de vida como viewDidLoad por você

Tudo isso são exemplos de IoC. DI é apenas um subconceito especializado em “fornecer dependências”.

Um telefone antigo sobre uma mesa com um cartão escrito “Don't call us, we'll call you”
A transferência do controle: isso é tudo o que IoC significa

DI trata de “como fornecer dependências”

DI, ou injeção de dependência, é a técnica mais representativa para implementar IoC.

No exemplo anterior de DIP, o OrderService recebia PayGateway pelo inicializador. Isso é DI: fornecer de fora um objeto necessário, em vez de criá-lo internamente.

Há três formas principais de injeção.

Forma de injeção Descrição Recomendação
Injeção pelo construtor Receber a dependência por init Mais recomendada (imutável, dependências obrigatórias explícitas)
Injeção por método Receber como parâmetro do método Para dependências opcionais
Injeção por propriedade Atribuir à propriedade após a criação Não recomendada, pois se torna opcional e var

No Swift, recomenda-se a injeção pelo inicializador. A dependência pode ser fixada com let, o que é mais seguro, e é fácil fornecer objetos falsos (Mocks) nos testes.

A relação pode ser resumida assim.

Para seguir o princípio DIP, transferimos o controle na direção do IoC e usamos DI como meio concreto.

Esta frase resume da forma mais concisa a relação entre os três termos.

Diagrama em um quadro branco conectando com setas três caixas: WHY, WHO e HOW
Separar em por quê, quem e como evita confusão

Perguntas frequentes

P. DI e IoC não são a mesma coisa?

Não. IoC é o conceito mais amplo, enquanto DI é uma das várias formas de implementar IoC. Toda DI é IoC, mas nem todo IoC é DI.

P. Seguir o DIP significa usar DI automaticamente?

Não necessariamente. DIP é o princípio de “depender de abstrações”; DI ocorre quando uma implementação dessa abstração é fornecida externamente. Princípio e técnica são coisas diferentes.

P. Posso usar DI sem uma biblioteca como o Swinject?

Sim. Passar a dependência diretamente pelo inicializador, como no exemplo de Swift acima, também é DI. Swinject e Factory apenas automatizam o processo; a DI não exige uma biblioteca.


Em resumo: DIP é por quê, o princípio; IoC é quem, o controle; e DI é como, a técnica. Separar as três perguntas dessa forma evita confusão.

Quando você entende que estão em níveis diferentes, o código relacionado a DI passa a parecer completamente diferente. Espero que este artigo tenha ajudado a organizar tudo com clareza. Força!

Continue lendo