Al crear una app con Swift, llega un momento en que las clases no dejan de crecer.
Solo añadiste un botón, pero ya necesitas versiones para modo claro, modo oscuro, iOS y iPadOS; cuantas más combinaciones, más explota el número de clases.
Vayamos al grano. El patrón Bridge de Swift separa «qué hace (abstracción)» de «cómo lo hace (implementación)» en jerarquías de clases distintas, para gestionar ambos ejes mediante suma y no mediante multiplicación.
En este artículo veremos por qué se produce la explosión de subclases, cómo la evita el patrón Bridge y el código Swift correspondiente.
¿Por qué se produce la explosión de subclases?
La explosión de subclases aparece cuando se representan dos ejes de cambio independientes usando solo herencia.
Veamos un ejemplo.
Supongamos que existe una clase Remote que controla televisores y radios.
¿Y si además necesitas dos tipos de mando: «mando básico» y «mando avanzado»?
Las combinaciones quedarían así.
- Mando básico × TV
- Mando básico × radio
- Mando avanzado × TV
- Mando avanzado × radio
Ya son 4 clases.
Si añades un altavoz como dispositivo, pasan a ser 6; si añades un mando por voz, llegan a 9.
Con dos ejes, usar solo herencia hace que el número de clases crezca por multiplicación (m×n), no por suma (m+n).
Eso es la explosión de subclases. Cada eje adicional aumenta exponencialmente la carga de mantenimiento.
¿Cómo evita esto el patrón Bridge de Swift?
La idea clave es solo una.
Separa los dos ejes cambiantes en jerarquías independientes y conéctalos mediante una referencia (composición).
- Capa de abstracción: tipos de mando (básico/avanzado)
- Capa de implementación: tipos de dispositivo (TV/radio)
El mando no hereda del dispositivo. En su lugar, mantiene la interfaz del dispositivo como una propiedad.
Primero definimos la capa de implementación como un protocolo. Solo contiene las operaciones mínimas que debe ofrecer un dispositivo.
// Capa de implementación: capacidades mínimas que debe ofrecer un dispositivo
protocol Device {
var volume: Int { get set }
func enable()
func disable()
}
Ahora el mando, que es la capa de abstracción, solo referencia Device. Es inyección, no herencia.
// Capa de abstracción: posee el dispositivo y delega en él
class RemoteControl {
let device: Device // Punto de conexión del puente
init(device: Device) { self.device = device }
func togglePower() { /* device.enable/disable Llamada */ }
}
Esta propiedad device es el puente (Bridge) que conecta ambas capas.
¿Qué cambia al ampliarlo en la práctica?
Esto es lo que noté al ampliar el código personalmente.
Para añadir un mando avanzado, hereda de RemoteControl y agrega una clase únicamente a la jerarquía de mandos.
// Extensión de la abstracción: no se modifica ni una línea del código de dispositivos
class AdvancedRemote: RemoteControl {
func mute() {
var d = device
d.volume = 0 // Delegar en la capa de implementación
}
}
En cambio, para añadir un dispositivo nuevo, como un altavoz, solo tienes que adoptar el protocolo Device. El código del mando se reutiliza sin cambios.
Los dos ejes son completamente independientes, así que ampliar uno no exige crear nuevas combinaciones del otro.
La diferencia se aprecia claramente en la tabla.
| Categoría | Solo herencia | Patrón Bridge |
|---|---|---|
| Crecimiento de clases | Multiplicación (m×n) | Suma (m+n) |
| 3 tipos de mando × 4 tipos de dispositivo | 12 | 7 |
| Al añadir un dispositivo | Añadir tantos como tipos de mando haya | Añadir solo 1 |
| Sustitución en tiempo de ejecución | Difícil | Flexible mediante inyección |
Con 3 tipos de mando y 4 de dispositivo, pasas de 12 clases a 7. Cuanto mayores sean los ejes, mayor será la diferencia.
¿Cuándo usarlo y cuándo evitarlo?
El patrón Bridge no es la respuesta para todas las situaciones. Si lo aplicas en exceso, el código puede volverse más complejo.
Encaja bien cuando:
- Ves dos o más ejes que cambian de forma independiente
- Quieres cambiar la implementación en tiempo de ejecución (por ejemplo, API real ↔ mock)
- Quieres separar claramente las implementaciones por plataforma y por tema
Puedes evitarlo cuando:
- Solo existe un eje o las combinaciones seguirán fijas en 2 o 3
- La estructura es sencilla, pero dividirla de antemano en jerarquías sería sobreingeniería
Como los protocolos y la inyección de dependencias son muy naturales en Swift, es posible que ya estuvieras usando el patrón Bridge sin conocer su nombre.
Preguntas frecuentes (Q&A)
P. ¿En qué se diferencia del patrón Adapter?
Adapter sirve para conectar después código incompatible ya creado. Bridge tiene como objetivo separar desde el diseño los dos ejes.
P. ¿Tengo que usar protocol necesariamente?
Sí. En Swift, definir la capa de implementación como protocol es lo más natural. Frente a una clase abstracta, un protocolo también permite usar tipos por valor (struct) como implementaciones.
P. ¿También puedo usarlo con SwiftUI?
Sí. Si una vista recibe mediante inyección un protocolo de fuente de datos y solo se encarga de renderizar, eso ya es una estructura Bridge.
Si te estresaba que las subclases no dejaran de crecer, recuerda hoy una sola cosa.
Cuando pienses «esta clase cambia por dos motivos», ese es el momento de separar los dos ejes.
Empieza trasladando a mano un ejemplo pequeño; se asimila antes de lo que parece. ¡Espero que tu código se vuelva más ligero!
Lecturas recomendadas
- [Fundamentos de Swift #4] Cómo usar Swift guard correctamente: salida temprana para tumbar la pirámide de la fatalidad
- [Fundamentos de Swift #5] Mapa completo del manejo de errores en Swift: ¿cuándo usar throws, try?, try! y Result?
- Patrón Mediator en Swift: guía completa (deja la comunicación entre objetos en manos de un mediador)

