Patrón Strategy: Algoritmos Intercambiables en Runtime
design-patternsbehavioralstrategy

¿Qué es el Patrón Strategy?
El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan.
classDiagram
class Context {
-strategy: Strategy
+setStrategy(strategy)
+executeStrategy()
}
class Strategy {
<<interface>>
+execute(data)
}
class ConcreteStrategyA {
+execute(data)
}
class ConcreteStrategyB {
+execute(data)
}
Context o--> Strategy
Strategy <|.. ConcreteStrategyA
Strategy <|.. ConcreteStrategyBEn términos simples: en lugar de tener un if-else gigante para decidir qué algoritmo usar, extraes cada algoritmo a su propia clase y los intercambias según necesites.
El Problema que Resuelve
class PaymentProcessor {
process(method: string, amount: number): void {
if (method === 'credit_card') {
this.processCreditCard(amount);
} else if (method === 'paypal') {
this.processPayPal(amount);
} else if (method === 'crypto') {
this.processCrypto(amount);
}
}
}Problemas: La clase crece sin control, difícil de testear, viola Open/Closed.
La Solución: Strategy
1. Definir la Interfaz Strategy
interface PaymentData {
amount: number;
currency: string;
customerId: string;
}
interface PaymentResult {
success: boolean;
transactionId: string;
fee: number;
netAmount: number;
}
/** Contrato que toda estrategia de pago debe implementar */
interface PaymentStrategy {
readonly name: string;
process(data: PaymentData): Promise<PaymentResult>;
calculateFee(amount: number): number;
validate(data: PaymentData): boolean;
}2. Implementar Estrategias Concretas
/** Estrategia para pagos con tarjeta de crédito */
class CreditCardStrategy implements PaymentStrategy {
readonly name = 'credit_card';
async process(data: PaymentData): Promise<PaymentResult> {
const fee = this.calculateFee(data.amount);
return {
success: true,
transactionId: `cc_${Date.now()}`,
fee,
netAmount: data.amount - fee,
};
}
calculateFee(amount: number): number {
return amount * 0.029 + 0.30;
}
validate(data: PaymentData): boolean {
return data.amount <= 10000;
}
}
/** Estrategia para pagos con criptomonedas */
class CryptoStrategy implements PaymentStrategy {
readonly name = 'crypto';
async process(data: PaymentData): Promise<PaymentResult> {
const fee = this.calculateFee(data.amount);
return {
success: true,
transactionId: `crypto_${Date.now()}`,
fee,
netAmount: data.amount - fee,
};
}
calculateFee(amount: number): number {
return amount * 0.01;
}
validate(data: PaymentData): boolean {
return data.amount >= 10;
}
}3. Crear el Contexto
/** Procesador que acepta múltiples estrategias de pago */
class PaymentProcessor {
private strategies = new Map<string, PaymentStrategy>();
private currentStrategy: PaymentStrategy | null = null;
registerStrategy(strategy: PaymentStrategy): this {
this.strategies.set(strategy.name, strategy);
return this;
}
setStrategy(name: string): this {
const strategy = this.strategies.get(name);
if (!strategy) throw new Error(`Unknown strategy: ${name}`);
this.currentStrategy = strategy;
return this;
}
async process(data: PaymentData): Promise<PaymentResult> {
if (!this.currentStrategy) throw new Error('No strategy selected');
if (!this.currentStrategy.validate(data)) throw new Error('Validation failed');
return this.currentStrategy.process(data);
}
/** Selecciona automáticamente la estrategia con menor comisión */
selectBestStrategy(amount: number): string {
let bestStrategy: PaymentStrategy | null = null;
let lowestFee = Infinity;
for (const strategy of this.strategies.values()) {
if (strategy.validate({ amount, currency: 'USD', customerId: '' })) {
const fee = strategy.calculateFee(amount);
if (fee < lowestFee) {
lowestFee = fee;
bestStrategy = strategy;
}
}
}
if (!bestStrategy) throw new Error('No valid strategy');
this.currentStrategy = bestStrategy;
return bestStrategy.name;
}
}Ejemplo de Uso
const processor = new PaymentProcessor()
.registerStrategy(new CreditCardStrategy())
.registerStrategy(new CryptoStrategy());
processor.setStrategy('crypto');
const result = await processor.process({
amount: 99.99,
currency: 'USD',
customerId: 'cust_123',
});
const bestMethod = processor.selectBestStrategy(500);
console.log(`Best method for $500: ${bestMethod}`);Beneficios del Patrón Strategy
| Aspecto | Beneficio |
|---|---|
| Open/Closed | Agregar estrategias sin modificar código |
| Single Responsibility | Cada estrategia tiene su propia clase |
| Testabilidad | Cada estrategia se testea aisladamente |
| Flexibilidad | Cambiar algoritmo en runtime |
Cuándo Usar Strategy
- Cuando necesitas diferentes variantes de un algoritmo
- Cuando quieres evitar condicionales múltiples
- Cuando tienes clases que solo difieren en su comportamiento
Cuándo NO Usar Strategy
- Si solo hay 2-3 algoritmos simples que nunca cambiarán
- Si la selección del algoritmo no cambia en runtime
Conclusión
El patrón Strategy es una herramienta poderosa para manejar múltiples algoritmos de forma limpia y extensible.
En el próximo artículo exploraremos el Patrón Adapter, que nos permite integrar interfaces incompatibles.
Basado en "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).