Padrão Bridge: Desacoplando Abstração e Implementação

O Padrão Bridge é um padrão de projeto estrutural que se concentra em desacoplar uma abstração de sua implementação, permitindo que ambas evoluam de forma independente. Para compreender o valor do Bridge, é útil primeiro examinar duas estruturas hierárquicas distintas que frequentemente se entrelaçam em projetos de software.

Estruturas de Hierarquia

  1. Hierarquia de Abstração (Funcionalidade): Esta hierarquia foca na extensão das funcionalidades. Imagine uma classe base Relatorio. Podemos criar uma subclasse RelatorioDetalhado que adiciona mais informações ou recursos, estendendo o comportamento do relatório base. Novas classes nesta hierarquia adicionam novas capacidades ou refinam as existentes, mas mantêm o mesmo "o que" está sendo feito.
  2. Hierarquia de Implementação: Esta hierarquia lida com a variação na maneira como um comportamento é executado. Considere uma interface GeradorPDF com um método gerar(). Poderíamos ter diferentes implementações, como GeradorPDFiText ou GeradorPDFApachePDFBox, cada uma fornecendo uma forma concreta de gerar um PDF. Aqui, as subclasses variam o "como" o comportamento é realizado, mas aderem ao mesmo "o que" (a interface definida).

Problema e Solução do Padrão Bridge

Em sistemas mais simples, é comum que a hierarquia de funcionalidade e a hierarquia de implementação estejam rigidamente acopladas. Por exemplo, uma classe RelatorioPDF pode herdar diretamente de GeradorPDFiText. Se precisarmos de um RelatorioHTML ou quisermos mudar o motor PDF para um GeradorPDFApachePDFBox, seríamos forçados a criar uma nova classe como RelatorioHTMLApachePDFBox, levando a uma explosão de classes e um sistema inflexível.

O padrão Bridge propõe separar essas duas hierarquias. Ele introduz uma "ponte" (o objeto implementor) que permite que classes na hierarquia de abstração (funcionalidade) deleguem suas operações a classes na hierarquia de implementação. Isso significa que podemos modificar a funcionalidade sem alterar a implementação e vice-versa, promovendo a flexibilidade e a extensibilidade.

Exemplo de Código: Representação de Formas Geométricas

Vamos ilustrar o Bridge com um exemplo de desenho de formas geométricas. Teremos uma hierarquia de abstração para as formas (Shape) e uma hierarquia de implementação para as APIs de desenho (DrawingAPI).

1. Hierarquia de Abstração (Formas)

Começamos com a inetrface Shape, que define o contrato para qualquer forma. As classes concretas (Circle, Rectangle) estenderão esta interface e usarão uma instância de DrawingAPI para realizar suas operações de desenho.


// Implementor: Define a interface para as classes de implementação
public interface DrawingAPI {
    void drawCircle(double x, double y, double radius);
    void drawRectangle(double x, double y, double width, double height);
}

Agora, a Abstração base que contém uma referência ao implementor:


// Abstraction: Classe base para as formas geométricas
public abstract class Shape {
    protected DrawingAPI drawingAPI;

    public Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }

    public abstract void draw();
}

Abstrações refinadas que estendem a funcionalidade ou representam formas específicas:


// Refined Abstraction: Círculo
public class Circle extends Shape {
    private double x, y, radius;

    public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public void draw() {
        drawingAPI.drawCircle(x, y, radius);
    }
}


// Refined Abstraction: Retângulo
public class Rectangle extends Shape {
    private double x, y, width, height;

    public Rectangle(double x, double y, double width, double height, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        drawingAPI.drawRectangle(x, y, width, height);
    }
}

2. Hierarquia de Implementação (APIs de Desenho)

Esta hierarquia define diferentes formas de renderizar as formas. Cada classe implementa a interface DrawingAPI de uma maneira específica (por exemplo, usando SVG ou Canvas).


// Concrete Implementor: Implementação para desenho em SVG
public class SvgDrawingAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.println("Desenhando Círculo [SVG] em (" + x + "," + y + ") com raio " + radius);
    }

    @Override
    public void drawRectangle(double x, double y, double width, double height) {
        System.out.println("Desenhando Retângulo [SVG] em (" + x + "," + y + ") com largura " + width + " e altura " + height);
    }
}


// Concrete Implementor: Implementação para desenho em Canvas
public class CanvasDrawingAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.println("Renderizando Círculo [Canvas] em (" + x + "," + y + ") com raio " + radius);
    }

    @Override
    public void drawRectangle(double x, double y, double width, double height) {
        System.out.println("Renderizando Retângulo [Canvas] em (" + x + "," + y + ") com largura " + width + " e altura " + height);
    }
}

3. Exemplo de Uso (Main)

No método principal, podemos combinar qualquer forma com qualquer API de desenho, demonstrando o desacoplamento.


public class DrawingApplication {
    public static void main(String[] args) {
        // Desenhar formas usando a API SVG
        DrawingAPI svgAPI = new SvgDrawingAPI();
        Shape svgCircle = new Circle(10, 20, 5, svgAPI);
        Shape svgRectangle = new Rectangle(30, 40, 10, 8, svgAPI);

        System.out.println("--- Desenho com SVG API ---");
        svgCircle.draw();
        svgRectangle.draw();

        // Desenhar formas usando a API Canvas
        DrawingAPI canvasAPI = new CanvasDrawingAPI();
        Shape canvasCircle = new Circle(50, 60, 15, canvasAPI);
        Shape canvasRectangle = new Rectangle(70, 80, 25, 12, canvasAPI);

        System.out.println("\n--- Desenho com Canvas API ---");
        canvasCircle.draw();
        canvasRectangle.draw();

        // É possível até mesmo usar a mesma forma com diferentes APIs
        System.out.println("\n--- Mudando a API de desenho para uma forma existente ---");
        Shape anotherSvgCircle = new Circle(100, 100, 20, new SvgDrawingAPI());
        anotherSvgCircle.draw(); // Usa SVG
        // Se quisermos mudar a API de desenho de 'anotherSvgCircle' em tempo de execução,
        // precisaríamos de um setter ou recriar o objeto, mas a flexibilidade é evidente.
    }
}

Papéis no Padrão Bridge

  • Abstraction (Shape): Define a interface de alto nível. Mantém uma referência para um Implementor.
  • Refined Abstraction (Circle, Rectangle): Estende a Abstraction, adicionando funcionalidades ou características específicas.
  • Implementor (DrawingAPI): Define a interface para as classes de implementação. Não precisa corresponder diretamente à interface da Abstraction.
  • Concrete Implementor (SvgDrawingAPI, CanvasDrawingAPI): Implementa a interface do Implementor, fornecendo a implemetnação concreta para a Abstraction.

No exemplo, o campo drawingAPI na classe Shape atua como a "ponte" que conecta a hierarquia de abstração com a hierarquia de implementação.

Padrões de Projeto Relacionados

  • Temlpate Method: O Bridge frequentemente usa o Template Method, onde a Abstração define um esqueleto de um algoritmo (template) e delega as etapas específicas a métodos abstratos que são implementados pelos Concrete Implementors.
  • Abstract Factory: Pode ser usado para criar instâncias de Concrete Implementor, abstraindo a criação de famílias de objetos relacionados.
  • Adapter: Enquanto o Bridge conecta duas hierarquias diferentes (abstração/implementação), o Adapter adapta a interface de uma classe existente para outra interface que o cliente espera. O Bridge permite que a abstração e a implementação variem independentemente, o Adapter faz com que classes incompatíveis funcionem juntas.

Benefícios da Abordagem Bridge

A principal vantagem do padrão Bridge é a sua capacidade de separar o "o quê" do "como". Isso resulta em:

  • Maior Flexibilidade: Permite que a abstração e a implementação sejam desenvolvidas e estendidas independentemente, sem afetar uma à outra. Por exemplo, podemos adicionar novas formas sem modificar as APIs de desenho existentes, e vice-versa.
  • Redução de Acoplamento: Diminui a dependência entre as classes de abstração e implementação, facilitando a manutenção e a reutilização do código.
  • Extensibilidade Simplificada: A adição de novas funcionalidades (novas formas) ou novas implementações (novas APIs de desenho) é mais simples, pois cada lado da ponte pode ser estendido de forma isolada.

Tags: design-patterns java bridge-pattern software-architecture object-oriented-programming

Publicado em 6-14 04:24 por Thomas