EstruturalAvançado6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Bridge

Separa uma abstração da sua implementação para que as duas variem independentemente, trocando uma hierarquia que cresce em produto por duas que crescem em soma.

Em 10 segundos

Quando algo varia em duas dimensões, herança gera uma classe por combinação (NotificacaoUrgenteSMS, NotificacaoUrgenteEmail…). O Bridge separa as dimensões em duas hierarquias ligadas por composição: o crescimento vira soma em vez de produto.

  • #composicao
  • #duas-dimensoes
  • #desacoplamento
  • #gof
Analogia

Controle remoto e aparelho

Existem controles simples e controles com comandos avançados; existem TVs, caixas de som e projetores. Ninguém fabrica um 'controle-avançado-de-projetor' como produto indivisível — os controles falam um protocolo, e qualquer aparelho que o entenda funciona. Assim, um controle novo serve para todos os aparelhos, e um aparelho novo funciona com todos os controles.

01Conceito

O problema

Herança resolve bem uma dimensão de variação. Com duas, ela obriga a escolher qual delas vira a hierarquia principal — e a outra precisa ser replicada dentro de cada ramo.

O sintoma é fácil de reconhecer: classes com nomes compostos por duas ideias, como NotificacaoUrgenteSMS, e código quase idêntico repetido entre os ramos.

A conta é o argumento mais convincente. Com M tipos e N implementações, a herança cruzada produz M×N classes; a composição produz M+N. Com quatro e três, doze viram sete — e a distância aumenta a cada adição, além de eliminar a duplicação entre ramos irmãos.

A confusão mais comum é com Strategy, e a diferença é de escopo e de intenção. Strategy troca um algoritmo dentro de um objeto: é uma decisão local, muitas vezes de um método só. Bridge separa duas hierarquias inteiras que existem por si, cada uma com seus próprios subtipos e sua própria evolução. Mecanicamente parecidos; arquiteturalmente, de tamanhos diferentes.

Com Adapter a distinção é temporal e de propósito. Adapter é remediação: você tem duas interfaces incompatíveis que já existem e precisa conciliá-las. Bridge é projeto: você antecipa duas dimensões de variação e desenha as duas hierarquias desde o início. Adapter conserta o passado, Bridge organiza o futuro.

02Conceito

Quando a segunda dimensão é real

O Bridge só se paga se as duas dimensões forem genuinamente independentes. Aplicá-lo onde elas não são cria indireção sem ganho e, pior, permite combinações inválidas.

O teste é direto: qualquer valor de A pode se combinar com qualquer valor de B e produzir algo que faça sentido? Se metade das combinações não deve existir, o modelo está errado — e o padrão passa a exigir validações em tempo de execução para proibir o que o tipo deveria proibir.

Outro teste é de evolução: as duas dimensões mudam por motivos diferentes e em ritmos diferentes? Canais de entrega mudam quando a infraestrutura muda; tipos de notificação mudam quando o produto muda. São eixos independentes de verdade, e por isso o padrão encaixa.

Se a resposta a essas perguntas for não, o caminho costuma ser mais simples: uma dimensão por herança e a outra por um parâmetro, ou apenas Strategy no ponto específico que varia.

Sem o padrão × com o padrão

Bridge: M + N classes

  • Notificacao e NotificacaoUrgente de um lado
  • CanalEmail, CanalSMS, CanalPush do outro
  • um canal novo é uma classe, e serve a todos os tipos
  • a lógica de 'urgente' existe uma vez só

O custo é uma indireção e a exigência de que as dimensões sejam mesmo independentes — se certas combinações não fizerem sentido, o padrão passa a permitir estados inválidos.

A troca não é entre mais e menos código, é entre multiplicação e soma. Quanto mais valores cada dimensão tiver, maior a diferença a favor do Bridge.
Como se organiza
  • Abstração: Notificaçãoo QUE — guarda um Canal

    • NotificaçãoUrgenterefina o comportamento

  • Implementação: Canalo COMO — hierarquia separada

    • CanalEmailentrega por e-mail

    • CanalSMSentrega por SMS

São duas árvores lado a lado, não uma dentro da outra. A ponte é a referência que a abstração guarda para um implementador — e é ela que permite combinar qualquer folha de cima com qualquer folha de baixo.
Passo a passoO fluxo, passo a passo
  1. 1

    Separar as dimensões

    Identifique o QUE (abstração) e o COMO (implementação) — eixos que variam sozinhos.

  2. 2

    Contrato do implementador

    Interface enxuta com as operações primitivas de que a abstração precisa.

  3. 3

    Abstração guarda a ponte

    A abstração recebe o implementador por composição e delega a ele.

  4. 4

    Variar cada lado

    Novos tipos e novos canais entram como classes novas — sem produto cartesiano.

03Anatomia

Navegue pelas camadas

Camada 1/3Abstração

Camada 1

Abstração

o que se faz

Define a operação de alto nível em termos de negócio e guarda uma referência ao implementador. Não sabe como a entrega acontece — só que existe alguém capaz de fazê-la.

No código

class Notificacao { constructor(protected canal: Canal) {} }

Se violar: abstração que faz `if (canal instanceof CanalSMS)` recriou o acoplamento entre as dimensões e desfez a ponte.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// Lado B: COMO entregar (varia sozinho)
interface Canal {
  entregar(texto: string): void;
}

class CanalEmail implements Canal {
  entregar(texto: string): void {
    console.log("email:", texto);
  }
}
class CanalSMS implements Canal {
  entregar(texto: string): void {
    console.log("sms:", texto);
  }
}

// Lado A: O QUE notificar (varia sozinho)
class Notificacao {
  // a "ponte": composicao, nao heranca
  constructor(protected canal: Canal) {}

  enviar(msg: string): void {
    this.canal.entregar(msg);
  }
}

class NotificacaoUrgente extends Notificacao {
  enviar(msg: string): void {
    this.canal.entregar("[URGENTE] " + msg);
    this.canal.entregar("[URGENTE] repetindo: " + msg);
  }
}

// 2 tipos x 2 canais = 4 combinacoes, com 4 classes (nao 4 subclasses cruzadas)
new Notificacao(new CanalEmail()).enviar("bem-vindo");
new NotificacaoUrgente(new CanalSMS()).enviar("servidor caiu");
06No mundo real

Casos de uso

Notificações por múltiplos canais

Cenário

Um produto envia boas-vindas, alertas de segurança e lembretes de cobrança por e-mail, SMS, push e webhook — e ambos os lados crescem com o tempo.

Como o padrão entra

Tipos de notificação formam uma hierarquia; canais formam outra. Adicionar WhatsApp é uma classe nova que passa a servir a todos os tipos existentes, sem tocar em nenhum.

Trade-off: Canais têm capacidades diferentes: SMS tem limite de caracteres, push aceita ação, e-mail aceita HTML. A interface comum acaba sendo o menor denominador, e explorar o que cada canal tem de melhor exige furar a abstração.

Drivers de persistência

Cenário

Um repositório precisa funcionar sobre PostgreSQL em produção, SQLite em desenvolvimento e memória nos testes, com várias entidades diferentes.

Como o padrão entra

Os repositórios de domínio são a abstração; os drivers são a implementação. Trocar de banco é injetar outro driver, sem alterar nenhum repositório.

Trade-off: Bancos diferem em transações, tipos e recursos avançados. Uma interface comum honesta é pequena; se ela crescer para expor especificidades, a promessa de intercambialidade se torna falsa e o teste em memória para de refletir a produção.

Renderização multiplataforma

Cenário

Um aplicativo desenha as mesmas telas em web, mobile e desktop, com componentes que evoluem por conta própria.

Como o padrão entra

Os componentes são a abstração; os renderizadores de cada plataforma são a implementação. Um componente novo funciona nas três; uma plataforma nova recebe todos os componentes.

Trade-off: Convenções de plataforma divergem (navegação, gestos, atalhos). Forçar uniformidade produz interfaces que parecem estrangeiras em todo lugar — às vezes a duplicação consciente vale mais que a ponte.

A ponte que ainda faz instanceof

Compila, roda, passa no code review. E quebra depois.

Há duas hierarquias e uma referência ao canal — mas a abstração continua ramificando por tipo concreto. A composição só mudou o endereço do acoplamento.

class Notificacao {
  constructor(protected canal: Canal) {}
  enviar(msg: string) {
    // A "ponte" existe... e a abstracao ainda pergunta o tipo.
    if (this.canal instanceof CanalSMS) {
      this.canal.entregar(msg.slice(0, 160));
    } else if (this.canal instanceof CanalEmail) {
      this.canal.entregar("<p>" + msg + "</p>");
    } else {
      this.canal.entregar(msg);
    }
  }
}

// Canal novo = editar Notificacao. As duas hierarquias
// voltaram a se acoplar — so que agora com instanceof.

O que quebra

  • Ao adicionar canalÉ preciso reabrir a abstração e acrescentar mais um ramo — exatamente o M×N que o Bridge evitava.
  • No testeTestar Notificacao exige montar canais reais; o polimorfismo não cobre a decisão.
  • Na leituraA abstração 'sabe demais' sobre cada implementação — a ponte é cosmética.

A saída: A abstração fala só com a interface (entregar). Diferenças de formato ficam no próprio canal, ou em adaptadores por canal — nunca em instanceof na abstração.

08Cuidado

Armadilhas

  1. Aplicar sem a segunda dimensão

    Bridge com uma implementação só é indireção pura, e costuma nascer de 'um dia vamos precisar trocar'. Enquanto a segunda implementação não existe, a interface é adivinhação — e quando ela chega, quase sempre não encaixa. Espere a segunda dimensão aparecer de verdade antes de construir a ponte.

  2. Interface do implementador que incha

    Cada método adicionado à interface precisa ser implementado por todas as implementações. Uma interface que começou com um método e chegou a doze transforma 'adicionar um canal' de tarefa de uma hora em projeto de uma semana — e é o que faz times abandonarem o padrão no meio.

  3. Combinações inválidas permitidas

    Se nem todo par abstração × implementação faz sentido, o padrão passa a permitir estados que o domínio proíbe, e a proteção precisa virar checagem em tempo de execução. Isso é sinal de que as dimensões não eram independentes — e de que o modelo precisa ser revisto antes do padrão.

09Decisão

Quando usar × quando evitar

Use quando

3
  • O conceito varia em duas dimensões independentes.
  • Você quer trocar a implementação em tempo de execução.
  • Abstração e implementação evoluem por motivos diferentes.

Evite quando

3
  • Há uma dimensão só de variação.
  • As dimensões não são independentes de fato.
  • O sistema é pequeno e estável.

Testar o que ficou

3 armadilhas de Bridge viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • React: reconciler × renderersO mesmo reconciler dirige react-dom, React Native e three.js — abstração e implementação evoluem separadas.
  • Drivers de banco de dadosO SQL que você escreve não muda quando o driver por baixo troca de protocolo.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Abstração × implementação variam independente.
const remoto = new Controle(new TvSony());
remoto.ligar();

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • Duas hierarquias separadas a manter, com a ponte entre elas explícita no código
  • A indireção entre abstração e implementação dificulta seguir a chamada até o efeito

Não vale se: abstração e implementação não variam de forma independente — sem esses dois eixos, uma herança simples basta.

Costuma ser confundido com

Onde isto aparece

Continue por aqui