Strategy (Estrategia) es un patrón de diseño de comportamiento que define una familia de algoritmos intercambiables y coloca cada uno de ellos en una clase separada (Strategy). El patrón permite seleccionar un algoritmo sobre la marcha: el código cliente funciona a través de una interfaz Strategy común, y la implementación concreta se sustituye en tiempo de ejecución. En iOS, el patrón se implementa mediante Protocol + clases de estrategia, en Android — mediante Interface + implementaciones. Strategy es uno de los 23 patrones GoF, ampliamente utilizado para procesamiento de pagos, validación, ordenamiento y filtrado de datos. Para más detalles — consulte la descripción original de GoF.
Puntos clave
Strategy es uno de los 23 patrones GoF (Gang of Four), descrito en el libro "Design Patterns: Elements of Reusable Object-Oriented Software" (1994). El patrón resuelve el problema de seleccionar un algoritmo en tiempo de ejecución. En lugar de escribir una sola clase con múltiples sentencias condicionales (if-else, switch), Strategy propone extraer cada algoritmo en una clase separada con una interfaz común. El contexto (la clase que utiliza la estrategia) mantiene una referencia a la interfaz Strategy y delega la ejecución a la estrategia concreta.
La estructura del patrón incluye tres elementos: Context mantiene una referencia a Strategy y llama a su método; Strategy (interfaz) declara un método común para todos los algoritmos; ConcreteStrategy implementa la interfaz y contiene el algoritmo concreto. El cliente crea la estrategia deseada y la pasa al contexto a través de un constructor, setter o parámetro de método. El contexto no sabe qué estrategia específica se está ejecutando — solo trabaja con la interfaz.
| Componente | Rol | Ejemplo |
|---|---|---|
| Context | Mantiene una referencia a Strategy | PaymentProcessor, Sorter |
| Strategy | Interfaz común para algoritmos | Protocol PaymentStrategy |
| ConcreteStrategy | Implementación concreta del algoritmo | CardPayment, PayPalPayment |
Principio Open/Closed — la principal ventaja de Strategy. El sistema está abierto para extensión (se puede añadir una nueva estrategia) y cerrado para modificación (el código del contexto no necesita cambiar). Sin el patrón, añadir un nuevo algoritmo requiere modificar la clase existente, lo que viola OCP y aumenta el riesgo de errores de regresión. Strategy también reduce el tamaño de las clases: en lugar de una clase de 200 líneas con un switch-case, se obtienen 6 clases de 20 líneas cada una.
Strategy en Swift se implementa mediante Protocol (interfaz de estrategia) y clases o estructuras de estrategia. Los protocolos de Swift admiten tipos asociados y restricciones genéricas, lo que proporciona flexibilidad al diseñar estrategias. El contexto suele ser una clase ViewModel o servicio que acepta la estrategia en init o mediante una propiedad. El patrón se utiliza ampliamente en proyectos iOS para manejo de eventos, animaciones, formato de datos y estrategias de UI.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Concrete Strategies
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Enviando solicitud a la API bancaria
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Redirigir al SDK de PayPal
return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
}
}
// 3. Context
class PaymentProcessor {
private var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func setStrategy(_: PaymentStrategy) {
strategy = strategy
}
func processPayment(amount: Decimal) async throws -> PaymentResult {
return try await strategy.pay(amount: amount)
}
}
// Uso
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy en SwiftUI — el patrón se integra naturalmente con MVVM. Un ViewModel contiene una propiedad de estrategia y llama a su método ante una acción del usuario. La View de SwiftUI recibe datos mediante @Published o @State — la estrategia oculta los detalles de implementación de la View. Por ejemplo, una estrategia de validación de texto (emailValidator, phoneValidator) se intercambia según el tipo de campo de entrada. La combinación de Strategy con SwiftUI proporciona flexibilidad sin heredar de UIKit.
Strategy en Kotlin utiliza Interface a nivel de lenguaje e interfaces funcionales (SAM) para simplificar. Kotlin admite lambdas, lo que permite pasar algoritmos como funciones sin declarar una clase de estrategia separada. En Android, el patrón se usa en ViewModel y Use Cases para aislar algoritmos de carga de datos, caché y manejo de errores. Los proyectos Android con Clean Architecture usan Strategy para inyectar diferentes implementaciones de repositorio según banderas (mock, real, caché).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Concrete Strategies
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// API bancaria mediante Retrofit
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Integración con SDK de PayPal
return PaymentResult(success = true, transactionId = "pp_${UUID.randomUUID()}")
}
}
// 3. Context
class PaymentProcessor(
private val strategy: PaymentStrategy
) {
fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
return PaymentProcessor(strategy)
}
suspend fun processPayment(amount: BigDecimal): PaymentResult {
return strategy.pay(amount)
}
}
// Uso en ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Procesamiento del resultado
}
}
}
Strategy con Hilt/Dagger — en proyectos Android, las estrategias a menudo se inyectan mediante DI. Hilt proporciona una implementación concreta de PaymentStrategy a través de @Binds o @Provides. Esto permite cambiar la estrategia sin modificar el código del contexto — simplemente cambie el módulo DI para otra compilación (debug/release). Por ejemplo, se inyecta MockPaymentStrategy para depuración, una estrategia bancaria real para producción. La combinación de Strategy + DI proporciona la máxima flexibilidad.
Strategy vs State — estructuralmente los patrones son idénticos: ambos usan composición con una interfaz y clases concretas. La diferencia está en el propósito: Strategy selecciona un algoritmo independiente, State controla el comportamiento del objeto según su estado. En State, el propio contexto cambia la estrategia cuando cambia el estado; en Strategy, el contexto no controla el cambio — el cliente establece explícitamente el algoritmo. Las estrategias no se conocen entre sí, mientras que los estados pueden transicionar entre sí.
Strategy vs Command — Command encapsula una sola acción como objeto, Strategy encapsula un conjunto de algoritmos intercambiables. Command es "qué hacer" (una sola llamada execute), Strategy es "cómo hacerlo" (un algoritmo de varios pasos). Command se usa para colas, ejecución diferida, deshacer/rehacer. Strategy se usa para elegir cómo realizar una tarea en tiempo de ejecución. Los comandos pueden ser parametrizados con estrategias, combinando ambos patrones.
| Característica | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Propósito | Algoritmos intercambiables | Comportamiento según estado | Encapsulación de solicitud | Esqueleto de algoritmo |
| Cambio | Explícitamente por el cliente | Automáticamente por el contexto | Por el cliente o cola | Por herencia |
| Nivel | Objeto (composición) | Objeto (composición) | Objeto | Clase (herencia) |
Strategy vs Template Method — ambos patrones definen algoritmos pero de diferentes maneras. Template Method usa herencia: una clase base define el esqueleto del algoritmo (método de plantilla), las subclases sobrescriben pasos individuales. Strategy usa composición: el algoritmo está completamente externalizado a una clase separada. Template Method es más simple para casos con una estructura de algoritmo fija, Strategy — cuando los algoritmos son completamente diferentes y pueden cambiar dinámicamente.
Procesamiento de pagos — el ejemplo clásico de Strategy. El carrito de compras de una tienda online contiene una lista de artículos, y el método de pago es elegido por el usuario. Cada método (tarjeta, PayPal, Apple Pay, Google Pay, criptomoneda) es una estrategia separada con una firma común pay(amount). El contexto PaymentProcessor no sabe cómo se procesa exactamente el pago — llama al método común. Añadir un nuevo método de pago no requiere modificar el código del carrito.
Validación de datos — Strategy se usa para diferentes reglas de validación del mismo campo. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementan una interfaz común ValidationStrategy con el método validate(input). Un formulario de registro usa un conjunto de estrategias para verificar cada campo. Las estrategias de validación se pueden combinar en una cadena (Chain of Responsibility) o aplicarse todas a la vez en un bucle. Esto reemplaza largas comprobaciones if-else con una colección de validadores polimórficos.
// Estrategia de ordenamiento
protocol SortingStrategy {
func sort<T>(_ items: [T]) -> [T] where T: Comparable
}
struct QuickSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* quicksort */ items }
}
struct MergeSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* mergesort */ items }
}
class SortedDataSource<T> {
private var strategy: SortingStrategy
func display(_ items: [T]) { let sorted = strategy.sort(items) }
}
Autenticación — en aplicaciones móviles, las estrategias de autenticación se cambian según el proveedor. AuthStrategy con los métodos login(), logout(), getToken() se implementa para EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. El contexto AuthManager acepta la estrategia mediante DI o fábrica. Esto permite añadir nuevos proveedores de autenticación sin modificar la pantalla de inicio de sesión. El patrón Strategy es la base de muchas bibliotecas OAuth y Firebase Authentication.
Preguntas frecuentes
Strategy está justificado cuando tienes 3+ algoritmos que pueden cambiar o expandirse. Si hay 2 algoritmos y son estables — un simple if-else es menos costoso. Usa Strategy cuando los algoritmos se usan en diferentes partes de la aplicación, cuando necesitas intercambiar algoritmos en tiempo de ejecución, o cuando cada algoritmo requiere sus propias dependencias y pruebas.
No, son patrones diferentes con una estructura similar. Strategy — el cliente selecciona explícitamente un algoritmo, y las estrategias son independientes. State — el objeto mismo cambia su comportamiento cuando su estado interno cambia, y los estados pueden transicionar entre sí. En State, el contexto gestiona los cambios de estado; en Strategy, lo hace el código del cliente.
Sí, en Swift y Kotlin una estrategia se puede pasar como closure o lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Esto simplifica el código para casos simples pero pierde nombramiento y documentación. Para 1-2 algoritmos, un closure es suficiente; para 4+, es mejor usar clases separadas.
Cada estrategia se prueba con una prueba unitaria separada usando dependencias mock. El Context se prueba con una estrategia mock — verificando que el contexto llama al método de la estrategia y pasa los parámetros correctos. En Swift, usa XCTest + protocolos para mocks; en Kotlin, usa MockK o Mockito. La principal ventaja: cada estrategia se prueba de forma aislada sin configuración compleja.
Sí, Strategy es uno de los 23 patrones descritos en el libro "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, Helm, Johnson, Vlissides, 1994). Pertenece al grupo de patrones de comportamiento. Alias: Policy. El código de ejemplo original en Smalltalk-80 está disponible en la edición original de GoF.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también