Carregando agora

Programando voltado à interface, e não à implementação

Ao programar orientado a objetos, uma das melhores práticas que os desenvolvedores podem adotar é a abordagem “programar voltado à interface, e não à implementação”. Essa filosofia permite maior flexibilidade, manutenibilidade e escalabilidade no código. Mas o que isso realmente significa?

O que é “Programar voltado à interface”?

Quando dizemos “programar voltado à interface”, estamos nos referindo à prática de projetar sistemas onde as classes interagem entre si por meio de interfaces (ou classes abstratas) em vez de trabalhar diretamente com implementações concretas. Em outras palavras, o código depende de contratos (interfaces), e não de detalhes (implementações).

Isso é valioso porque facilita a substituição das implementações sem modificar o código que depende delas. Em vez de acoplar o sistema a uma classe específica, você define comportamentos abstratos e qualquer classe que implemente aquela interface pode ser usada.

Benefícios dessa abordagem

  1. Baixo acoplamento: O código que depende de uma interface não precisa conhecer os detalhes da implementação, tornando-o menos dependente de mudanças e mais fácil de atualizar.
  2. Flexibilidade: Ao utilizar interfaces, diferentes implementações podem ser trocadas ou expandidas sem alterar o restante do código.
  3. Testabilidade: Interfaces facilitam o uso de mock objects em testes unitários, já que qualquer implementação pode ser usada em vez de uma implementação concreta.

Exemplo em Java

Vamos explorar como isso funciona na prática.

1. Definindo a interface

public interface Animal {
    void fazerSom();
}

Aqui temos uma interface simples chamada Animal. Ela define um método fazerSom(), mas não fornece qualquer detalhe sobre como isso será implementado.

2. Implementando a interface

Agora, vamos criar duas classes que implementam a interface Animal.

public class Cachorro implements Animal {
    @Override
    public void fazerSom() {
        System.out.println("O cachorro faz: Au au!");
    }
}

public class Gato implements Animal {
    @Override
    public void fazerSom() {
        System.out.println("O gato faz: Miau!");
    }
}

Cada classe (Cachorro e Gato) fornece sua própria implementação do método fazerSom(). Contudo, ambas seguem o “contrato” da interface Animal.

3. Programando voltado à interface

Agora, observe como podemos utilizar essa estrutura programando voltado à interface:

public class Main {
    public static void main(String[] args) {
        Animal meuAnimal = new Cachorro();
        meuAnimal.fazerSom();  // Saída: O cachorro faz: Au au!

        meuAnimal = new Gato();
        meuAnimal.fazerSom();  // Saída: O gato faz: Miau!
    }
}

O código acima não depende de uma implementação específica de Animal. Ele apenas sabe que qualquer classe que implemente Animal deve ter o método fazerSom(). Isso nos permite trocar facilmente a implementação de Cachorro para Gato (ou qualquer outro Animal que criemos no futuro) sem precisar alterar a lógica no restante do programa.

Exemplo mais avançado: Injeção de Dependências

Em projetos maiores, é comum usar frameworks de injeção de dependências, como o Spring, que automaticamente fornecem implementações de interfaces. Isso torna ainda mais clara a vantagem de programar voltado à interface.

Interface

public interface BancoDeDados {
    void conectar();
}

Implementações

public class MySQL implements BancoDeDados {
    @Override
    public void conectar() {
        System.out.println("Conectado ao MySQL");
    }
}

public class PostgreSQL implements BancoDeDados {
    @Override
    public void conectar() {
        System.out.println("Conectado ao PostgreSQL");
    }
}

Injeção de Dependências

public class Aplicacao {
    private BancoDeDados bancoDeDados;

    // Constructor injection
    public Aplicacao(BancoDeDados bancoDeDados) {
        this.bancoDeDados = bancoDeDados;
    }

    public void iniciar() {
        bancoDeDados.conectar();
    }

    public static void main(String[] args) {
        BancoDeDados mysql = new MySQL();
        Aplicacao app1 = new Aplicacao(mysql);
        app1.iniciar();  // Saída: Conectado ao MySQL

        BancoDeDados postgres = new PostgreSQL();
        Aplicacao app2 = new Aplicacao(postgres);
        app2.iniciar();  // Saída: Conectado ao PostgreSQL
    }
}

Aqui, a classe Aplicacao depende da interface BancoDeDados, e não de uma implementação específica. Isso torna a Aplicacao altamente flexível e capaz de trabalhar com qualquer banco de dados que implemente a interface.

Conclusão

Programar voltado à interface e não à implementação promove um código mais modular, flexível e de fácil manutenção. O uso de interfaces permite que diferentes partes de um sistema sejam desenvolvidas e mantidas de maneira independente, facilitando a evolução do software ao longo do tempo. Ao utilizar essa abordagem em Java, você pode construir sistemas mais robustos e adaptáveis, prontos para lidar com mudanças e expansões futuras.

Se quiser ver mais exemplos, acesse meu GitHub: neootavio.

Otávio Elias

Graduando em Ciências Econômicas e Formado em Sistemas de Informação pela Universidade Federal de Ouro Preto, estou sempre estudando e compartilhando conhecimentos através da escrita. Fundador do blog Team Strategy possuo 11 anos de experiência no setor bancário e investidor por paixão.

Otávio Elias

Especialista em Investimento - CEA Fundador do site Team Strategy, programador por hobby e investidor por paixão. Formado em Sistemas de Informação e atualmente cursando Ciências Econômicas.