Ao criar um app com Swift, chega um momento em que as classes começam a se multiplicar sem parar.
Você só adicionou um botão, mas já precisa de versões para modo claro, modo escuro, iOS e iPadOS; quanto mais combinações, mais as classes explodem.
Vamos direto ao ponto. O padrão Bridge do Swift separa “o que fazer (abstração)” de “como fazer (implementação)” em hierarquias de classes distintas, permitindo gerenciar os dois eixos por soma, não por multiplicação.
Neste artigo, veremos por que a explosão de subclasses acontece, como o padrão Bridge a evita e o código Swift correspondente.
Por que a explosão de subclasses acontece?
A explosão de subclasses acontece quando dois eixos independentes de mudança são representados usando apenas herança.
Vamos a um exemplo.
Suponha que exista uma classe Remote que controla TVs e rádios.
E se você também precisasse de dois tipos de controle: “controle básico” e “controle avançado”?
As combinações ficariam assim.
- Controle básico × TV
- Controle básico × rádio
- Controle avançado × TV
- Controle avançado × rádio
Já são 4 classes.
Ao adicionar uma caixa de som como dispositivo, são 6; ao adicionar um controle por voz, o total sobe para 9.
Com dois eixos, usar apenas herança faz o número de classes crescer por multiplicação (m×n), não por soma (m+n).
Isso é a explosão de subclasses. Cada novo eixo aumenta exponencialmente o esforço de manutenção.
Como o padrão Bridge do Swift evita isso?
A ideia central é uma só.
Separe os dois eixos que mudam em hierarquias independentes e conecte-os por uma referência (composição).
- Camada de abstração: tipos de controle (básico/avançado)
- Camada de implementação: tipos de dispositivo (TV/rádio)
O controle não herda do dispositivo. Em vez disso, mantém a interface do dispositivo como uma propriedade.
Primeiro, definimos a camada de implementação como um protocolo. Ela contém apenas as operações mínimas que um dispositivo deve oferecer.
// Camada de implementação: recursos mínimos que um dispositivo deve oferecer
protocol Device {
var volume: Int { get set }
func enable()
func disable()
}
Agora, o controle, que é a camada de abstração, apenas referencia Device. Isso é injeção, não herança.
// Camada de abstração: mantém o dispositivo e delega a ele
class RemoteControl {
let device: Device // Ponto de conexão da ponte
init(device: Device) { self.device = device }
func togglePower() { /* device.enable/disable Chamada */ }
}
Essa propriedade device é a ponte (Bridge) que conecta as duas camadas.
O que muda quando fazemos uma extensão de verdade?
Foi isso que percebi ao expandir o código por conta própria.
Para adicionar um controle avançado, basta herdar de RemoteControl e criar mais uma classe apenas na hierarquia de controles.
// Extensão da abstração: nenhuma linha do código dos dispositivos é alterada
class AdvancedRemote: RemoteControl {
func mute() {
var d = device
d.volume = 0 // Delegar à camada de implementação
}
}
Por outro lado, para adicionar um novo dispositivo, como uma caixa de som, basta adotar o protocolo Device. O código do controle continua sendo reutilizado sem alterações.
Os dois eixos ficam totalmente independentes, então ampliar um não exige criar novas combinações do outro.
A diferença fica clara na tabela.
| Categoria | Somente herança | Padrão Bridge |
|---|---|---|
| Crescimento das classes | Multiplicação (m×n) | Soma (m+n) |
| 3 tipos de controle × 4 tipos de dispositivo | 12 | 7 |
| Ao adicionar um dispositivo | Adicionar um por tipo de controle | Adicionar apenas 1 |
| Troca em tempo de execução | Difícil | Livre por meio de injeção |
Com 3 tipos de controle e 4 tipos de dispositivo, 12 classes viram 7. Quanto maiores os eixos, maior a diferença.
Quando usar e quando evitar?
O padrão Bridge não é a resposta para todas as situações. Usá-lo em excesso pode deixar o código mais complexo.
Ele se encaixa bem quando:
- Há dois ou mais eixos que mudam de forma independente
- Você quer trocar a implementação em tempo de execução (por exemplo, API real ↔ mock)
- Você quer separar claramente implementações por plataforma e por tema
Você pode dispensá-lo quando:
- Há apenas um eixo ou as combinações continuarão fixas em 2 ou 3
- A estrutura é simples, mas dividi-la previamente em hierarquias seria overengineering
Como protocolos e injeção de dependência são naturais no Swift, talvez você já estivesse usando o padrão Bridge sem conhecer seu nome.
Perguntas frequentes (Q&A)
P. Qual é a diferença para o padrão Adapter?
Adapter serve para conectar posteriormente códigos incompatíveis que já foram criados. Bridge serve para separar os dois eixos desde o início, na etapa de projeto.
P. Preciso usar protocol obrigatoriamente?
Sim. No Swift, definir a camada de implementação como protocol é a opção mais natural. Ao usar um protocolo em vez de uma classe abstrata, tipos por valor (struct) também podem ser implementações.
P. Também posso usar com SwiftUI?
Sim. Se uma view recebe um protocolo de fonte de dados por injeção e fica responsável apenas pela renderização, isso já é uma estrutura Bridge.
Se o crescimento constante das subclasses estava causando estresse, lembre-se de uma única coisa hoje.
Quando pensar “esta classe muda por dois motivos”, é hora de separar os dois eixos.
Comece transcrevendo um exemplo pequeno à mão; você se acostuma mais rápido do que imagina. Espero que seu código fique mais leve!
Leitura recomendada
- [Swift básico #4] Como usar Swift guard corretamente: saída antecipada para derrubar a pirâmide da desgraça
- [Swift básico #5] O mapa completo do tratamento de erros no Swift: quando usar throws, try?, try! e Result?
- Padrão Mediator no Swift: guia completo (delegue a comunicação entre objetos a um mediador)

