Pular para o conteúdo
Estruturais

Bridge

Desacopla uma abstração da sua implementação, para que as duas possam variar de forma independente.

Intenção

Dividir uma classe que tem duas dimensões ortogonais de variação em hierarquias separadas -- uma abstração e uma implementação -- conectadas por uma ponte (composição), para que qualquer um dos lados possa evoluir sem afetar o outro.

Problema

Você tem um conceito que pode variar em dois eixos independentes. Por exemplo, formas e backends de renderização, ou notificações e canais de entrega. Usar apenas herança leva a uma explosão combinatória de subclasses (CircleSvgRenderer, CircleCanvasRenderer, SquareSvgRenderer, ...) e torna adicionar uma nova variante em qualquer um dos eixos desproporcionalmente caro.

Solução

Extraia uma das dimensões para uma hierarquia de interface separada (a implementação). A hierarquia original (a abstração) mantém uma referência a um objeto de implementação e delega o trabalho específico de plataforma a ele. Ambas as hierarquias podem ser estendidas de forma independente, e novas combinações são formadas por composição em vez de subclassificação.

Participantes

  • Abstraction -- define a interface de controle de alto nível; mantém uma referência a um Implementor
  • RefinedAbstraction -- estende a Abstraction com comportamento mais rico
  • Implementor -- declara a interface para operações específicas de implementação
  • ConcreteImplementor -- fornece uma implementação concreta da interface Implementor

Vantagens

  • Evita a explosão combinatória de subclasses vinda de duas dimensões ortogonais
  • Abstração e implementação podem ser estendidas de forma independente
  • Detalhes de implementação podem ser trocados ou substituídos em tempo de execução
  • Segue o Princípio Aberto/Fechado -- novas abstrações e implementações não exigem modificar código existente

Desvantagens

  • Aumenta a complexidade ao adicionar camadas extras de indireção
  • Exige uma análise cuidadosa antecipada para identificar as dimensões corretas de variação
  • Excessivo se houver apenas uma implementação ou se a abstração nunca mudar

Analogia do mundo real

Um controle remoto universal de TV (abstração) funciona com qualquer marca de televisão (implementação). O controle define botões como ligar, volume, e canal, enquanto cada marca de TV implementa como esses comandos são executados internamente. Você pode comprar um controle novo ou uma TV nova de forma independente -- eles se comunicam através de um protocolo infravermelho padrão (a ponte).

Casos de uso

  • Formas que podem ser renderizadas com diferentes backends gráficos (SVG, Canvas, OpenGL)
  • Notificações que podem ser entregues por diferentes canais (e-mail, SMS, push)
  • Controles remotos de dispositivos que abstraem múltiplos tipos de aparelho (TV, rádio, smart speaker)
  • Camadas de persistência onde objetos de negócio se conectam a diferentes backends de armazenamento (SQL, NoSQL, arquivo)
  • Frameworks de UI multiplataforma onde widgets se conectam à renderização nativa da plataforma

Exemplos de código

Shape.java
interface Renderer {
    void renderCircle(double x, double y, double radius);
    void renderRect(double x, double y, double w, double h);
}

final class SvgRenderer implements Renderer {
    @Override
    public void renderCircle(double x, double y, double radius) {
        System.out.printf("<circle cx="%.0f" cy="%.0f" r="%.0f" />%n", x, y, radius);
    }

    @Override
    public void renderRect(double x, double y, double w, double h) {
        System.out.printf("<rect x="%.0f" y="%.0f" width="%.0f" height="%.0f" />%n", x, y, w, h);
    }
}

final class CanvasRenderer implements Renderer {
    @Override
    public void renderCircle(double x, double y, double radius) {
        System.out.printf("ctx.arc(%.0f, %.0f, %.0f, 0, 2*PI); ctx.stroke();%n", x, y, radius);
    }

    @Override
    public void renderRect(double x, double y, double w, double h) {
        System.out.printf("ctx.strokeRect(%.0f, %.0f, %.0f, %.0f);%n", x, y, w, h);
    }
}

abstract class Shape {
    protected final Renderer renderer;

    protected Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();
}

final class Circle extends Shape {
    private final double x, y, radius;

    Circle(Renderer renderer, double x, double y, double radius) {
        super(renderer);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public void draw() {
        renderer.renderCircle(x, y, radius);
    }
}

final class Rectangle extends Shape {
    private final double x, y, w, h;

    Rectangle(Renderer renderer, double x, double y, double w, double h) {
        super(renderer);
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    @Override
    public void draw() {
        renderer.renderRect(x, y, w, h);
    }
}

public class BridgeDemo {
    public static void main(String[] args) {
        // Same shapes, two independent rendering backends
        Shape svgCircle = new Circle(new SvgRenderer(), 10, 10, 5);
        Shape canvasCircle = new Circle(new CanvasRenderer(), 10, 10, 5);

        svgCircle.draw();
        canvasCircle.draw();
    }
}

Separa Shape (abstração) de Renderer (implementação) para que novas formas e novos backends de renderização possam ser adicionados de forma independente.