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.
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.
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.
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.
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
- 1
Separar as dimensões
Identifique o QUE (abstração) e o COMO (implementação) — eixos que variam sozinhos.
- 2
Contrato do implementador
Interface enxuta com as operações primitivas de que a abstração precisa.
- 3
Abstração guarda a ponte
A abstração recebe o implementador por composição e delega a ele.
- 4
Variar cada lado
Novos tipos e novos canais entram como classes novas — sem produto cartesiano.
Navegue pelas camadas
Camada 1/3Abstração
use ← →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.
Estrutura
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");from abc import ABC, abstractmethod
# Lado B: COMO entregar (varia sozinho)
class Canal(ABC):
@abstractmethod
def entregar(self, texto): ...
class CanalEmail(Canal):
def entregar(self, texto):
print("email:", texto)
class CanalSMS(Canal):
def entregar(self, texto):
print("sms:", texto)
# Lado A: O QUE notificar (varia sozinho)
class Notificacao:
# a "ponte": composicao, nao heranca
def __init__(self, canal):
self._canal = canal
def enviar(self, msg):
self._canal.entregar(msg)
class NotificacaoUrgente(Notificacao):
def enviar(self, msg):
self._canal.entregar("[URGENTE] " + msg)
self._canal.entregar("[URGENTE] repetindo: " + msg)
# 2 tipos x 2 canais = 4 combinacoes com 4 classes
Notificacao(CanalEmail()).enviar("bem-vindo")
NotificacaoUrgente(CanalSMS()).enviar("servidor caiu")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.
Armadilhas
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.
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.
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.
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
- AdapterConverte a interface de uma classe em outra que o cliente espera, permitindo que classes incompatíveis trabalhem juntas.
- StrategyEncapsula uma família de algoritmos intercambiáveis por trás de uma interface comum, deixando o cliente trocar a estratégia em tempo de execução.
- Abstract FactoryCria famílias inteiras de objetos relacionados através de uma interface única, garantindo que os produtos de uma mesma variante sempre combinem entre si.