Blog Formação DEV

Injeção e inversão de dependências

Este artigo mostrará como as técnicas de injeção e inversão de dependência podem desacoplar classes e facilitar a manutenção do seu código através de um exemplo prático onde desacoplaremos um projeto do banco de dados!
Injeção e inversão de dependências
Texto de: Geraldo Daros

Introdução

A injeção e a inversão de dependência são princípios cruciais para o desenvolvimento de software eficiente e de alta qualidade. Esses conceitos ajudam a criar sistemas mais modulares, flexíveis e fáceis de testar, permitindo uma melhor separação de preocupações e reduzindo o acoplamento entre componentes. Neste artigo, vamos explorar como aplicar a injeção e a inversão de dependência mediante exemplos práticos em Java, demonstrando como essas técnicas podem transformar a arquitetura do seu código e melhorar sua manutenção e extensibilidade.

Injeção de dependências

A injeção de dependência é uma técnica na qual uma classe recebe suas dependências de uma fonte externa, em vez de criá-las internamente. Isso significa que a responsabilidade pela criação e gerenciamento dessas dependências é transferida para a entidade que faz a injeção. Vamos ver como isso funciona na prática com um exemplo de CRUD utilizando um banco de dados MySQL.

Primeiro, vamos criar uma classe para juntar todos os comportamentos relativos ao acesso ao banco de dados. Vou chamar essa classe aqui de MySQL e deixarei os métodos com implementações de teste só para facilitar o exemplo:

 public class MySQL {
    public void inserir() {
        System.out.println("Inserindo com MySQL");
    }
    public void atualizar() {
        System.out.println("Atualizando com MySQL");
    }
    public void excluir() {
        System.out.println("Excluindo com MySQL");
    }
    public void buscar() {
        System.out.println("Buscando com MySQL");
    }
}

Depois disso, vamos querer acessar esses comportamentos da classe MySQL em outra classe, que vou chamar de Servico.

public class Servico {

    private final MySQL mySQL;

     public Servico(MySQL mySQL) {
      this.mySQL = mySQL;
     }

    public void criar() {
       this.mySQL.inserir();
    }
    public void buscarTodos() {
        this.mySQL.buscar();
    }
    public void atualizar() {
        this.mySQL.atualizar();
    }
    public void excluir() {
        this.mySQL.excluir();
    }
}

Após definir essa classe Servico, podemos chamá-la e acessar seus métodos da seguinte forma:

public class Main {
    public static void main(String[] args) {
        Servico servico = new Servico(new MySQL());
        servico.criar();
        servico.buscarTodos();
        servico.atualizar();
        servico.excluir();
    }
}

Resultado:
Inserindo com MySQL
Buscando com MySQL
Atualizando com MySQL
Excluindo com MySQL 

Nesse exemplo, estamos injetando uma dependência. Criamos a classe MySQL para gerenciar a conexão com o banco de dados e realizamos as operações CRUD. Em seguida, injetamos uma instância dessa classe na nossa classe Servico via construtor. Isso permite que Servico interaja com MySQL sem precisar conhecer detalhes específicos sobre a implementação do banco de dados.

Essa é uma forma interessante de trabalhar com as dependências, porém, se precisarmos usar outro banco de dados, como PostgreSQL, nosso código está fortemente acoplado ao MySQL. Como podemos resolver isso? Usando inversão de dependência!

Inversão de Dependência

A inversão de dependência é um princípio de design que sugere que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Além disso, abstrações não devem depender de detalhes; os detalhes devem depender de abstrações. Em termos mais simples, ao invés de um objeto criar suas dependências, ele deve receber as dependências de uma fonte externa.

Agora é que a “mágica” acontece!

Para aplicar a inversão de dependência, criaremos uma interface chamada BancoDeDados que abstrai as operações de um banco de dados:

public interface BancoDeDados {
    void inserir();
    void atualizar();
    void excluir();
    void buscar();
}

Essa interface define um contrato que todas as implementações de banco de dados devem seguir. Agora, criaremos uma classe que implementa essa interface para o MySQL, chamada MySQLImpl:

public class MySQLImpl implements BancoDeDados {

    @Override
    public void inserir() {
        System.out.println("Inserindo com MySQL");
    }

    @Override
    public void atualizar() {
        System.out.println("Atualizando com MySQL");
    }

    @Override
    public void excluir() {
        System.out.println("Excluindo com MySQL");
    }

    @Override
    public void buscar() {
        System.out.println("Buscando com MySQL");
    }
}

Agora, nós temos uma classe conectada ao banco de dados MySQL e segue as regras do nosso BancoDeDados. Até agora, não usamos nenhum tipo de inversão de dependência, estávamos “preparando o terreno”.

A inversão de dependência é, ao invés de o nosso Servico precisar de uma implementação da classe MySQL, ele dependerá de uma abstração, no nosso caso, nossa abstração BancoDeDados. E essa é a inversão de dependência, não é a nossa classe que depende de uma implementação, é a abstração que depende de uma implementação. Com isso, conseguimos desacoplar nosso serviço da implementação específica do banco de dados. Em vez de nosso serviço saber diretamente como interagir com o MySQL, ele interage com a abstração BancoDeDados, permitindo que troquemos a implementação do banco de dados sem alterar o Servico.

Refatorando a classe Servico, ela ficará assim:

public class Servico {

    private final BancoDeDados db;

     public Servico(BancoDeDados db) {
      this.db = db;
     }

    public void criar() {
       this.db.inserir();
    }
    public void buscarTodos() {
        this.db.buscar();
    }
    public void atualizar() {
        this.db.atualizar();
    }
    public void excluir() {
        this.db.excluir();
    }
}

Agora nossa classe Servico depende de uma classe que implementa da interface BancoDeDados. Quando formos usar nosso Servico, precisaremos passar a nova classe MySQLImpl para que então funcione com o novo código. Dessa forma:

Servico servico = new Servico(new MySQLImpl());

Perfeito, mas, até agora, isso não mudou nada! Só deu mais trabalho, criar interface, depender de abstração, criar uma classe Impl. Para que tudo isso?

Agora podemos usar o Servico com qualquer banco de dados que implemente BancoDeDados. Vamos criar uma implementação para o PostgreSQL:

public class PostgresImpl implements BancoDeDados {
    @Override
    public void inserir() {
        System.out.println("Inserindo com Postgres");
    }

    @Override
    public void atualizar() {
        System.out.println("Atualizando com Postgres");
    }

    @Override
    public void excluir() {
        System.out.println("Excluindo com Postgres");
    }

    @Override
    public void buscar() {
        System.out.println("Buscando com Postgres");
    }
}

Usando ambas implementações:

public class Main {
    public static void main(String[] args) {
        System.out.println("Usando implementação MySQL");
        Servico servicoComMySQL = new Servico(new MySQLImpl());
        servicoComMySQL.criar();
        servicoComMySQL.buscarTodos();
        servicoComMySQL.atualizar();
        servicoComMySQL.excluir();

        System.out.println("\\nUsando implementação Postgres");
        Servico servicoComPostgres = new Servico(new PostgresImpl());
        servicoComPostgres.criar();
        servicoComPostgres.buscarTodos();
        servicoComPostgres.atualizar();
        servicoComPostgres.excluir();
    }
}
Resultado:
Usando implementação MySQL
Inserindo com MySQL
Buscando com MySQL
Atualizando com MySQL
Excluindo com MySQL

Usando implementação Postgres
Inserindo com Postgres
Buscando com Postgres
Atualizando com Postgres
Excluindo com Postgres

Essa abordagem permite que o nosso código fique mais modular, e traz diversos benefícios, especialmente quando pensamos em teste e manutenção da aplicação.

Conclusão

A injeção e a inversão de dependência são práticas essenciais para criar software robusto e flexível. Elas permitem a criação de código desacoplado, facilitam a manutenção e promovem a reutilização de componentes. Implementar essas práticas em projetos Java, mesmo sem frameworks, pode tornar seu código mais limpo, modular e fácil de testar.

Sobre o autor
Cod3r

Cod3r

Com mais de 400 mil alunos, a Cod3r é uma das principais escolas de tecnologia do País. Um de seus produtos mais importantes é a Formação DEV, com objetivo de preparar os profissionais para o mercado.

Ótimo! Inscreveu-se com sucesso.

Bem-vindo de volta! Registou-se com sucesso.

Assinou com sucesso o Blog Formação DEV .

Sucesso! Verifique o seu e-mail para obter o link mágico para se inscrever.

As suas informações de pagamento foram atualizadas.

Seu pagamento não foi atualizado.