¿Has sufrido alguna vez un código en el que los if-else se multiplicaban sin parar para cada método de pago?
Cuando las opciones de ordenación, el cálculo de descuentos y la lógica de filtrado empiezan a enredarse en un único switch enorme, cada cambio resulta más intimidante.
Ahí es cuando entra en juego el patrón Strategy de Swift.
Hoy veremos desde una perspectiva práctica cómo intercambiar algoritmos como si fueran componentes usando protocolos y closures.
Empecemos por lo esencial.
El patrón Strategy separa qué hacer de cómo hacerlo, lo que permite intercambiar algoritmos desde el exterior.
En Swift puede implementarse de dos formas: con protocolos, la opción más pesada, o con closures, la más ligera.
Las veremos una por una a continuación.
¿Qué es el patrón Strategy? Resumen en 3 líneas
Fijemos primero la estructura antes de entrar en teorías complejas.
- Agrupar los algoritmos intercambiables bajo una especificación común (protocolo)
- Crear el comportamiento real como objetos Strategy o closures independientes
- El código cliente solo conoce la especificación, no los detalles concretos
Es como cambiar el arma de un personaje de videojuego.
El personaje solo necesita saber «atacar»; ya sea una espada, un arco o magia, el arma equipada se encarga del resto.
Cambias solo el arma sin tocar el código del personaje.
Ese intercambio es, en esencia, todo el patrón Strategy.
Implementación con protocolos (el enfoque clásico)
Empecemos por el enfoque con protocolos más académico.
Usaremos el cálculo de descuentos como ejemplo: miembros normales, miembros VIP y aplicación de cupones implican distintos métodos de cálculo.
El código siguiente define la especificación común que toda estrategia debe cumplir y una estrategia concreta.
// Especificación común que debe seguir toda estrategia de descuentos
protocol DiscountStrategy {
func discount(for price: Int) -> Int
}
// VIP Estrategia: 20% descuento
struct VIPDiscount: DiscountStrategy {
func discount(for price: Int) -> Int { price * 20 / 100 }
}
Ahora veamos la parte que contiene y utiliza esta estrategia.
Al objeto cliente no le importa qué estrategia recibe.
struct Checkout {
var strategy: DiscountStrategy // Punto donde se puede intercambiar la estrategia
func finalPrice(_ price: Int) -> Int {
price - strategy.discount(for: price)
}
}
// Cambiar solo la estrategia en el momento del pago
let cart = Checkout(strategy: VIPDiscount())
Aunque aparezca una nueva política de descuentos, no hay que tocar Checkout.
Basta con crear una nueva estructura que cumpla DiscountStrategy.
Ampliar la funcionalidad sin tocar el código existente: esa es la mayor ventaja que se aprecia en la práctica.
Implementación con closures (el enfoque propio de Swift)
Sin embargo, crear una estructura cada vez que la estrategia es sencilla resulta algo engorroso.
Envolver incluso la lógica simple en un protocolo puede hacer que el código sea innecesariamente verboso.
En estos casos, los closures son mucho más ligeros y naturales en Swift.
struct Checkout {
// Recibir la estrategia como una función
var discount: (Int) -> Int
func finalPrice(_ price: Int) -> Int {
price - discount(price)
}
}
// Definir la estrategia directamente en ese punto
let cart = Checkout(discount: { $0 * 20 / 100 })
Puedes escribir la lógica justo al pasarla, sin declarar un tipo independiente.
Para intercambiar algoritmos ligeros, como criterios de ordenación, condiciones de filtrado y transformaciones simples, este enfoque resulta mucho más limpio.
De hecho, sorted(by:) de la biblioteca estándar es precisamente este patrón Strategy basado en closures.
La estructura de ordenación permanece fija y solo se intercambia el criterio de comparación mediante un closure.
En otras palabras, ya lo usábamos todos los días.
Protocolos y closures: ¿cuándo usar cada uno?
He resumido en una tabla los criterios que uso en la práctica.
| Categoría | Enfoque con protocolos | Enfoque con closures |
|---|---|---|
| Situación adecuada | Estrategia compleja o con estado | Estrategia breve y sencilla |
| Reutilización | Fácil de reutilizar en varios lugares | Adecuado para un uso puntual |
| Cantidad de código | Requiere declarar tipos; más código | Definición inline; menos código |
| Pruebas | Fácil de verificar como tipo individual | Verificar la lógica de forma sencilla |
| Legibilidad | La intención queda clara por el nombre | Claro si es corto; confuso si es largo |
Este es mi criterio.
Si la estrategia tiene estado interno, se reutiliza en varias pantallas o debe expresar claramente su intención mediante el nombre, conviene usar un protocolo.
Si solo vas a intercambiar sobre la marcha una lógica de una o dos líneas, un closure es la opción adecuada.
No hay ningún problema en combinar ambos. Yo uso protocolos para las políticas generales y closures para las opciones detalladas.
Preguntas frecuentes (Q&A)
P. ¿En qué se diferencia el patrón Strategy de la herencia (sobrescritura)?
La herencia está ligada a una clase padre, por lo que el comportamiento queda fijado en tiempo de compilación.
El patrón Strategy, en cambio, permite intercambiar estrategias libremente durante la ejecución y no depende de una jerarquía de clases.
P. ¿En qué se diferencia de usar un switch de un enum?
Con switch hay que abrir y modificar el código existente cada vez que aparece un caso nuevo.
Con el patrón Strategy solo hay que «añadir» una nueva estrategia, reduciendo la necesidad de tocar el código existente.
En definitiva, la esencia del patrón Strategy es sencilla: sacar fuera las partes cambiantes para que sea fácil intercambiarlas.
Si aplicas los protocolos y closures aprendidos hoy según la situación, podrás escapar de ese agotador infierno de if-else. ¡Empieza refactorizando un pequeño fragmento de código!

