Padrões de Projetos - Bridge

De Aulas

Afluentes: Modelos, métodos e técnicas da engenharia de software

Definição

O padrão de projeto Bridge (Ponte) é um padrão estrutural que tem como objetivo desacoplar uma abstração da sua implementação, permitindo que ambas possam variar independentemente. Ele é útil quando você tem uma hierarquia de classes e quer evitar que as variações nas abstrações e nas implementações levem a uma explosão de subclasses.

A ideia principal é usar a composição em vez de herança para ligar as duas partes: uma classe abstrata (a abstração) mantém uma referência a uma interface ou classe que será responsável pela implementação.

Estrutura do padrão Bridge

  1. Abstração: Define a interface para o controle das operações e mantém uma referência para o objeto de implementação.
  2. Implementação (Implementor): Interface que define as operações que serão implementadas concretamente.
  3. Abstração Refinada: Uma subclasse de abstração que adiciona mais funcionalidades, mas ainda mantém a referência à implementação.
  4. Implementação Concreta: Implementa a interface de implementação, oferecendo comportamentos específicos.

Exemplo Simples em Java

Vamos supor que estamos desenvolvendo uma aplicação para diferentes dispositivos de controle remoto que operam TVs e rádios. O padrão Bridge nos ajuda a separar a abstração (o controle remoto) da implementação (o dispositivo controlado).

Dispositivo.java

A interface Dispositivo é a implementação que será usada pelas classes concretas TV e Radio.

// Implementor (interface de implementação)
interface Dispositivo {
    void ligar();
    void desligar();
    void ajustarVolume(int nivel);
}

TV.java

// Implementação concreta do dispositivo TV
class TV implements Dispositivo {
    private int volume;

    @Override
    public void ligar() {
        System.out.println("TV ligada");
    }

    @Override
    public void desligar() {
        System.out.println("TV desligada");
    }

    @Override
    public void ajustarVolume(int nivel) {
        this.volume = nivel;
        System.out.println("Volume da TV ajustado para: " + this.volume);
    }
}

Radio.java

// Implementação concreta do dispositivo Rádio
class Radio implements Dispositivo {
    private int volume;

    @Override
    public void ligar() {
        System.out.println("Rádio ligado");
    }

    @Override
    public void desligar() {
        System.out.println("Rádio desligado");
    }

    @Override
    public void ajustarVolume(int nivel) {
        this.volume = nivel;
        System.out.println("Volume do rádio ajustado para: " + this.volume);
    }
}

ControleRemoto.java

A classe abstrata ControleRemoto define a interface de controle, com métodos ligar(), desligar() e ajustarVolume(int nivel).

// Abstração do Controle Remoto
abstract class ControleRemoto {
    protected Dispositivo dispositivo;

    public ControleRemoto(Dispositivo dispositivo) {
        this.dispositivo = dispositivo;
    }

    public abstract void ligar();
    public abstract void desligar();
    public abstract void ajustarVolume(int nivel);
}

ControleSimples.java

As classes ControleSimples e ControleAvancado são abstrações refinadas que podem adicionar funcionalidades extras, mas ambas trabalham com qualquer tipo de dispositivo (TV ou Radio), demonstrando o desacoplamento entre a abstração e a implementação.

// Abstração refinada do controle remoto simples
class ControleSimples extends ControleRemoto {

    public ControleSimples(Dispositivo dispositivo) {
        super(dispositivo);
    }

    @Override
    public void ligar() {
        dispositivo.ligar();
    }

    @Override
    public void desligar() {
        dispositivo.desligar();
    }

    @Override
    public void ajustarVolume(int nivel) {
        dispositivo.ajustarVolume(nivel);
    }
}

ControleAvancado.java

// Abstração refinada do controle remoto avançado
class ControleAvancado extends ControleRemoto {

    public ControleAvancado(Dispositivo dispositivo) {
        super(dispositivo);
    }

    public void ligar() {
        System.out.println("Iniciando controle avançado...");
        dispositivo.ligar();
    }

    public void desligar() {
        System.out.println("Desligando com controle avançado...");
        dispositivo.desligar();
    }

    public void ajustarVolume(int nivel) {
        System.out.println("Controle avançado ajustando volume...");
        dispositivo.ajustarVolume(nivel);
    }
}

Classe principal do programa Main.java

Veja que isso nos permite adicionar novos tipos de dispositivos ou novos tipos de controles remotos sem modificar as classes existentes.

public class Main {
    public static void main(String[] args) {
        // Utilizando a abstração com uma TV
        Dispositivo tv = new TV();
        ControleRemoto controleSimplesTV = new ControleSimples(tv);
        controleSimplesTV.ligar();
        controleSimplesTV.ajustarVolume(10);
        controleSimplesTV.desligar();

        // Utilizando a abstração com um rádio
        Dispositivo radio = new Radio();
        ControleRemoto controleAvancadoRadio = new ControleAvancado(radio);
        controleAvancadoRadio.ligar();
        controleAvancadoRadio.ajustarVolume(5);
        controleAvancadoRadio.desligar();
    }
}

Vantagens e Desvantagens

O padrão Bridge oferece várias vantagens e desvantagens que precisam ser consideradas ao decidir utilizá-lo em um projeto de software. Aqui estão as principais:

Vantagens do Padrão Bridge

  1. Desacoplamento entre abstração e implementação:
    • O principal benefício do Bridge é separar a abstração da implementação, permitindo que ambos possam evoluir de forma independente. Isso ajuda a evitar a criação de muitas subclasses (evitando a "explosão de classes"), já que as variações são tratadas de maneira modular.
  2. Maior flexibilidade:
    • Permite variar tanto a abstração quanto a implementação sem precisar alterar a estrutura principal do código. Isso é útil em sistemas que precisam suportar várias plataformas ou dispositivos.
  3. Facilidade de manutenção:
    • Como a abstração e a implementação são desacopladas, a manutenção se torna mais simples, pois alterações em uma parte do código não afetam diretamente a outra. Isso reduz o impacto de mudanças.
  4. Facilita extensões futuras:
    • Adicionar novas implementações ou abstrações se torna mais simples e sem a necessidade de reescrever o código existente. Isso é especialmente útil em projetos que requerem expansões e atualizações frequentes.
  5. Reduz a duplicação de código:
    • Como o padrão Bridge evita que funcionalidades relacionadas à abstração e à implementação fiquem sobrepostas, há uma redução significativa na duplicação de código em comparações com padrões como herança.

Desvantagens do Padrão Bridge

  1. Complexidade inicial:
    • Implementar o padrão Bridge pode adicionar uma complexidade inicial ao projeto, pois envolve mais classes e interfaces, mesmo que a aplicação seja simples. Isso pode ser visto como um "overhead" em termos de estrutura.
  2. Sobrecarga de abstrações:
    • Em sistemas muito pequenos ou simples, o uso do Bridge pode ser desnecessário e criar um nível de abstração adicional que não traz muitos benefícios, tornando o código mais difícil de seguir e depurar.
  3. Maior número de classes:
    • O padrão exige a criação de mais classes e interfaces, o que pode tornar a navegação e a leitura do código um pouco mais complicada. Isso é uma desvantagem se o projeto não for grande o suficiente para se beneficiar dessa flexibilidade.
  4. Requer bom planejamento:
    • O padrão Bridge é mais vantajoso quando existe uma previsão clara de que tanto a abstração quanto a implementação vão evoluir e variar ao longo do tempo. Se esse cenário não for realista, o esforço adicional pode ser desperdiçado.
  5. Dificuldade em casos muito simples:
    • Para casos onde há poucas variantes de abstração ou implementação, o Bridge pode ser desnecessário e apenas complicar a solução. Nesse caso, padrões mais simples, como herança direta ou uma fábrica (Factory), poderiam ser suficientes.

Quando Usar o Padrão Bridge?

O Bridge é mais indicado quando:

  • Você sabe que haverá múltiplas variações tanto na abstração quanto na implementação.
  • Você deseja manter o código flexível para futuras expansões e manutenções.
  • O sistema requer mudanças frequentes ou integrações com diferentes plataformas/dispositivos.

Por outro lado, se o seu sistema é simples e não há variações significativas, o Bridge pode ser desnecessário e adicionar complexidade sem um benefício claro.