DEV Community

Rodrigo Vieira
Rodrigo Vieira

Posted on

Qual a diferença entre a inversão e a injeção de dependências?

É comum a confusão entre esses dois conceitos. E veremos neste artigo que há alguns bons motivos para isso :D

O Princípio da Inversão de Dependências

Considere o diagrama abaixo. Ele representa parte de um sistema de negativação de títulos. Quando um devedor deixa de pagar um título, a empresa pode enviar o mesmo para o SERASA.

Diagrama de parte de nossa solução de negativação de títulos

Note que a classe Departamento possui 2 dependências com classes de camadas mais baixas, e que lida com questões tecnológicas. O Departamento então busca os títulos a serem negativados em TitulosDAO (que acessa o banco de dados) e, de posse da lista de títulos, os envia à SERASA (que pode lidar com uma interface REST com o SERASA, por exemplo).

Para representarmos a dependência de uma classe por outra, criamos uma seta que aponte para a direção da dependência. Assim, existem 2 setas, saindo de Departamento e apontando para TitulosDAO e SERASA e, consequentemente, o módulo de uma camada superior está dependendo de módulos de uma camada inferior. Guarde essa informação!

Se pensarmos em classes Java, teremos algo parecido com os códigos abaixo:

package br.com.rodnet.negativador.dao;

import br.com.rodnet.negativador.dominio.Titulo;

import java.util.List;

public class TitulosDAO {

    public List<Titulo> emAtraso() {
        //implementacao
    }
}
Enter fullscreen mode Exit fullscreen mode
package br.com.rodnet.negativador.serasa;

import br.com.rodnet.negativador.dominio.Titulo;

import java.util.List;

public class Serasa {
    public void negativar(List<Titulo> titulos) {
    }
}
Enter fullscreen mode Exit fullscreen mode
package br.com.rodnet.negativador.dominio;

import br.com.rodnet.negativador.dao.TitulosDAO;
import br.com.rodnet.negativador.serasa.Serasa;

import java.util.List;

class Departamento {

    private TitulosDAO titulosDAO;
    private Serasa serasa;

    Departamento(TitulosDAO titulosDAO, Serasa serasa) {
        this.titulosDAO = titulosDAO;
        this.serasa = serasa;
    }

    void negativar(){
        List<Titulo> titulos = titulosDAO.emAtraso();
        serasa.negativar(titulos);
    }
}
Enter fullscreen mode Exit fullscreen mode

Embora essa estrutura seja simples, ela apresenta alguns problemas. A camada superior do sistema depende de abstrações da camada inferior. Consequentemente, se houver alguma alteração em classes como TituloDAO, é possível que a classe Departamento sofra alterações.

Esse problema parece pequeno e talvez a princípio não mereça tanta atenção, mas é algo bem antigo na nossa profissão. O tão falado livro (não sem razão) do Uncle Bob cita exemplos de uns 50 anos atrás, onde precisávamos escrever software que precisava, entre outras coisas, escrever nos discos que existiam na época (enormes, barulhentos e com pouca capacidade). E quando eles precisaram trocar os discos para elementos menores (com mais capacidade e, provavelmente, menos barulhentos), os programas precisaram ser reescritos, não somente no código que cuidava da leitura e escrita nos discos, mas em todos os demais códigos que dependiam deles. As vezes, as correções subiam até para camadas mais acima do sistema.

De lá pra cá, aprendemos (aprendemos?) que é importante que o nosso software utilize as tecnologias adequadas, e que esteja preparado para uma possível substituição delas.

"Mas não podemos dizer isso!" diria o narrador.

Então podemos dizer que:

Módulos de alto nível não devem incorporar (ou incluir) nada dos módulos de baixo nível. Ambos devem trabalhar apenas com abstrações (interfaces ou classes abstratas).
Abstrações não devem depender de detalhes. Detalhes é que devem depender de abstrações.

Assim, se revisitarmos as classes do nosso sistema, podemos concluir que as classes de alto nível dependem de classes de baixo nível (Departamento depende de TitulosDAO e de SERASA). Portanto, mudanças devem ser feitas.

Segundo a citação acima, o Departamento deveria depender de abstrações. No caso, abstrações das classes que ele depende hoje.

Para que isso seja possível, não temos muitas alternativas a não ser criarmos interfaces que sejam implementadas pelas dependências atuais de Departamento. Então, mãos à obra:

Podemos começar pelo TitulosDAO, extraindo uma interface do mesmo:

package br.com.rodnet.negativador.dominio;

import java.util.List;

public interface RepositorioTitulos {
    List<Titulo> emAtraso();
}
Enter fullscreen mode Exit fullscreen mode
package br.com.rodnet.negativador.dao;

import br.com.rodnet.negativador.dominio.RepositorioTitulos;
import br.com.rodnet.negativador.dominio.Titulo;

import java.util.List;

public class TitulosDAO implements RepositorioTitulos {

    @Override
    public List<Titulo> emAtraso() {
        //implementacao
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

e fazer o mesmo com SERASA:

package br.com.rodnet.negativador.dominio;

import java.util.List;

public interface ServicoDeProtecaoAoCredito {
    void negativar(List<Titulo> titulos);
}
Enter fullscreen mode Exit fullscreen mode
package br.com.rodnet.negativador.serasa;

import br.com.rodnet.negativador.dominio.ServicoDeProtecaoAoCredito;
import br.com.rodnet.negativador.dominio.Titulo;

import java.util.List;

public class Serasa implements ServicoDeProtecaoAoCredito {
    @Override
    public void negativar(List<Titulo> titulos) {
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora que temos abstrações das classes de camadas inferiores, podemos agora substituir as dependências de Departamento por elas:

package br.com.rodnet.negativador.dominio;

import java.util.List;

class Departamento {

    private RepositorioTitulos repositorioTitulos;
    private ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito;

    Departamento(RepositorioTitulos repositorioTitulos, ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito) {
        this.repositorioTitulos = repositorioTitulos;
        this.servicoDeProtecaoAoCredito = servicoDeProtecaoAoCredito;
    }

    void negativar(){
        List<Titulo> titulos = repositorioTitulos.emAtraso();
        servicoDeProtecaoAoCredito.negativar(titulos);
    }
}
Enter fullscreen mode Exit fullscreen mode

OBS: troquei também os nomes dos atributos para que ficassem mais condizentes com as novas abstrações.

Percebam que agora as classes TitulosDAO e SERASA podem ser alteradas à vontade, pois isso não acarretará em alterações em Departamento. Elas só precisam implementar as interfaces de domínio, da maneira que for melhor para elas.

Podemos inclusive pensar mais longe:

  • trocamos de empresa de negativação? Sem problemas. Basta criar uma nova classe que implemente ServicoDeProtecaoAoCredito
  • não estamos mais usando um banco de dados, mas um serviço externo de busca de títulos? Tudo bem! Basta trocarmos a classe que implementa RepositorioTitulos. O Departamento não deverá sofrer nenhuma alteração sequer!

Agora, observem como nosso diagrama ficou após todas essas mudanças:

Modelagem da solução após as refatorações realizadas

Naturalmente que existem mais elementos no nosso diagrama. Afinal, precisamos representar nossas novas interfaces. Mas reparem para as setas. Mais precisamente as setas que indicam herança (ou implementação, no nosso caso).

Lembra que pedi para você guardar a informação sobre o sentido das setas? Pois bem: antes, as setas apontavam para baixo. E agora, elas apontam para cima. Ou seja, houve uma inversão de quem depende de quem. Ou, se preferir, houve uma inversão das dependências. Agora são os módulos das camadas inferiores é que dependem dos módulos superiores.

Basicamente esse é o Princípio de Inversão de Dependências. E a sua definição mais básica é aquela citação feita lá em cima:

Módulos de alto nível não devem incorporar (ou incluir) nada dos módulos de baixo nível. Ambos devem trabalhar apenas com abstrações (interfaces ou classes abstratas).
Abstrações não devem depender de detalhes. Detalhes é que devem depender de abstrações.

E a injeção de dependências?

Bem, a injeção de dependências tem mais a ver com a maneira com que criamos nossos objetos.

Vamos ver novamente a classe Departamento:

package br.com.rodnet.negativador.dominio;

import java.util.List;

class Departamento {

    private RepositorioTitulos repositorioTitulos;
    private ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito;

    Departamento(RepositorioTitulos repositorioTitulos, ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito) {
        this.repositorioTitulos = repositorioTitulos;
        this.servicoDeProtecaoAoCredito = servicoDeProtecaoAoCredito;
    }

    void negativar(){
        List<Titulo> titulos = repositorioTitulos.emAtraso();
        servicoDeProtecaoAoCredito.negativar(titulos);
    }
}
Enter fullscreen mode Exit fullscreen mode

Em Java, independente do fato de usarmos ou não um framework, os objetos devem ser instanciados de alguma forma. E as formas mais conhecidas são:

Agora, pense no caso da classe Departamento. Se usarmos o operador new para instanciá-la, podemos fazer algo como:

var repositorioTitulos = new TitulosDAO();
var servicoDeProtecaoAoCredito = new Serasa();
var departamento = new Departamento(repositorioTitulos, servicoDeProtecaoAoCredito);
Enter fullscreen mode Exit fullscreen mode

E quem seria responsável por instanciar todas essas classes?
No passado, usávamos bastante os padrões criacionais do GoF (como Abstract Factory ou Factory Method). Depois, passamos a trabalhar com o pattern Registry e até mesmo com JNDI. E, resumidamente falando, tínhamos mais desvantagens do que vantagens.

Talvez num cenário com apenas 3 classes, os problemas não sejam tão visíveis, mas pense num sistema maior, com mais classes. Um grande problema era o fato de que essas classes "fábrica" viviam em constante mudança, o que as tornavam bastante instáveis. E devemos evitar mudanças constantes, tanto em classes quanto em módulos inteiros. Para maiores detalhes e informações sobre o problema de mudanças em módulos, vale a pena estudar:

No fundo, o SOLID também se preocupa com o excesso de mudanças, mas em classes. E por falar em SOLID, o Uncle Bob também fala sobre os princípios acima no seu livro.

Bem, feita a tangente e voltando para o problema principal: se você conhece o Spring, normalmente você resolveria o problema de criação das classes inserindo as famosas anotações @Autowired e, no mínimo @Component ou alguma outra parecida nas dependências. Algo como:

@Service
class Departamento {

    private RepositorioTitulos repositorioTitulos;
    private ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito;

    @Autowired
    Departamento(RepositorioTitulos repositorioTitulos, ServicoDeProtecaoAoCredito servicoDeProtecaoAoCredito) {
        this.repositorioTitulos = repositorioTitulos;
        this.servicoDeProtecaoAoCredito = servicoDeProtecaoAoCredito;
    }

    void negativar(){
        List<Titulo> titulos = repositorioTitulos.emAtraso();
        servicoDeProtecaoAoCredito.negativar(titulos);
    }
}
Enter fullscreen mode Exit fullscreen mode

e nas demais classes:

@Repository
public class TitulosDAO implements RepositorioTitulos {

    @Override
    public List<Titulo> emAtraso() {
        //implementacao
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode
@Component
public class Serasa implements ServicoDeProtecaoAoCredito {
    @Override
    public void negativar(List<Titulo> titulos) {
    }
}
Enter fullscreen mode Exit fullscreen mode

Repare que, quando você utiliza um framework como o spring, você não precisa instanciar essas classes, muito menos passá-las como parâmetro para Departamento. O spring as instancia e as "injeta" em Departamento, que também pode ser injetada em outra classe. Assim, ele consegue montar boa parte da árvore de objetos necessária para o funcionamento do sistema.

Naturalmente que ainda temos que instanciar manualmente algumas classes. Mas não podemos negar que a injeção de dependências é uma mão na roda quando temos que construir objetos com uma árvore de dependências complexa (dependência que depende de dependência, que depende de dependência...).

E aí? Ficou mais clara a diferença entre esses dois conceitos? Normalmente eles são usados juntos, e talvez por isso crie-se tanta confusão entre eles.

Até a próxima!

Top comments (0)