Strategy Pattern
Intención y problema
¿Cómo permitir que un comportamiento o algoritmo varíe independientemente de quien lo usa? Strategy encapsula alternativas intercambiables detrás de un punto estable de colaboración. Pertenece a behavioral-patterns: el cambio que interesa ocurre en el comportamiento seleccionado, aunque su implementación use composición de objetos o funciones.
El tratamiento GoF presenta una familia de algoritmos intercambiables. La síntesis académica de CMU sobre Strategy conserva esa intención. El escenario de precios desarrollado aquí es propio: sirve para examinar responsabilidades y costos, no para prescribir una jerarquía.
Supongamos que PricingService calcula precios y coordina su uso. StandardPricing, PremiumPricing y PromotionalPricing cambian por razones diferentes. Si sus reglas quedan entremezcladas con coordinación, cada modificación exige entender ambos problemas. La presión no es que exista una condición: es que una variación independiente invade consumidores que deberían permanecer estables.
Forces y punto de variación
Queremos extender políticas sin reescribir cada caller, pero también conservar un diseño comprensible. Un contrato demasiado genérico puede esconder necesidades incompatibles; uno demasiado ligado a la primera variante puede obligar a todas las demás a conocer sus detalles.
El variation point debe tener significado: calcular un precio con entradas, resultado y errores definidos. Intercambiable no significa idéntico resultado. Significa que todas las alternativas cumplen las obligaciones que PricingService espera: por ejemplo, moneda explícita, reglas de redondeo acordadas y ausencia de modificaciones inesperadas del carrito.
Otra fuerza es separar selección y ejecución sin perder visibilidad. Alguien debe decidir qué política corresponde al cliente y a la operación. Strategy permite localizar esa decisión; no determina automáticamente su propietario ni elimina su complejidad.
Estructura y colaboración
PricingService Context
↓ utiliza
PricingStrategy contrato estable
├── StandardPricing alternativa
├── PremiumPricing alternativa
└── PromotionalPricing alternativaEl Context mantiene la operación anfitriona y una referencia a la capacidad de cálculo. La Strategy expresa la conversación compartida. Las variantes encapsulan algoritmos y, cuando corresponde, configuración o colaboradores propios.
Un ensamblador puede suministrar la estrategia al crear el contexto; una configuración puede elegirla para cada operación. También puede cambiar durante ejecución si existe esa necesidad. El cambio dinámico es una posibilidad, no una condición obligatoria para reconocer el patrón.
El flujo conceptual es seleccionar política → proporcionar entradas → delegar cálculo → usar resultado. PricingService no necesita distinguir todas las variantes para ejecutarlas. Si debe revisar el tipo concreto después de cada cálculo, conviene preguntar si el contrato conserva realmente la colaboración común.
La composición ayuda a invertir dependencias: la coordinación se apoya en una abstracción del comportamiento que necesita, y las políticas satisfacen esa abstracción. Dibujar una interface no demuestra por sí solo Dependency Inversion; importa que las reglas concretas no gobiernen el contrato de todos los consumidores.
Objetos y alternativas funcionales
La formulación clásica puede declarar:
interface PricingStrategy
calculatePrice(pricingInput) → pricingResultUn lenguaje con funciones de primera clase puede expresar la misma selección:
calculatePrice = premiumPricing
PricingService uses calculatePrice(pricingInput)Lambdas, delegates y function objects reducen ceremony cuando el comportamiento cabe en una función. Un objeto puede ser más claro si la política administra configuración, estado o colaboradores con lifecycle propio. Una closure también puede capturar estado; ser función no demuestra pureza ni ausencia de dependencias.
Strategy Pattern no equivale a class hierarchy. La intención reside en comportamiento intercambiable detrás de una colaboración estable. Los autores GoF reconocen que las capacidades del lenguaje cambian la manifestación de patrones en su entrevista retrospectiva. No hace falta reconstruir clases que no agregan responsabilidades.
Strategy frente a un conditional
if customerType == Premium
calculate premium price
else if customerType == Promotional
calculate promotional price
else
calculate standard priceUn conditional pequeño, estable y localizado puede ser la solución más clara. Tampoco es un fallo que un selector conserve un switch para elegir la estrategia. El objetivo es contener la variación, no conseguir cero condiciones.
| Señal observada | Qué puede aportar Strategy | Qué revisar |
|---|---|---|
| Variantes proliferan y evolucionan por separado. | Algoritmos aislados. | Que compartan una conversación real. |
| Callers repiten selección y cálculo. | Separación de esas responsabilidades. | Un propietario explícito de la selección. |
| Cada política requiere pruebas independientes. | Ejecución directa de cada alternativa. | Invariantes comunes además de casos particulares. |
| Hay nuevas políticas comprometidas. | Extensión del punto de variación. | Costo frente a una función suministrada. |
pattern-selection mantiene la comparación con soluciones directas. Una expectativa vaga de «más políticas algún día» no justifica registros dinámicos, factories y configuración extensible. Si tres ramas cambian siempre juntas, separarlas puede aumentar navegación sin aislar un eje útil.
Strategy, State y Template Method
Strategy selecciona comportamiento para una tarea o contexto. State organiza comportamiento según el estado interno y sus transiciones. En el ejemplo de precios, elegir PremiumPricing no describe necesariamente un ciclo de vida del objeto. En un documento Draft → Review → Approved, las operaciones admitidas y las transiciones constituyen el problema central.
Ambos pueden mostrar Context → interface → implementación. Un UML similar no implica la misma intención. Que una estrategia conserve datos tampoco la convierte automáticamente en State: importa si el modelo administra estados y transiciones o alternativas de un algoritmo.
La formulación clásica de Template Method conserva un esqueleto de algoritmo mediante herencia y pasos redefinibles. Strategy suele suministrar un colaborador mediante composición. Si el protocolo anfitrión debe conservar una secuencia y solo admite variar pasos concretos, el primero puede expresar mejor la obligación. Si interesa intercambiar una política completa, el segundo puede ser más adecuado. Ninguno gana en todos los contextos.
Consecuencias, pruebas y límites
La separación permite probar PremiumPricing sin toda la coordinación de PricingService y agregar otra alternativa sin editar consumidores compatibles. También hace visible qué parte debe permanecer estable y qué parte puede evolucionar.
A cambio introduce indirection, selección, contratos y posiblemente tipos adicionales. La estrategia activa puede depender de configuración que el lector no ve en la llamada. Las variantes con estado necesitan reglas de sharing, concurrencia y liberación; compartir una instancia mutable entre operaciones puede alterar resultados.
Las pruebas útiles verifican obligaciones comunes, casos propios de cada política y elección correcta. Un doble de prueba ayuda a aislar coordinación, pero no reemplaza comprobar que las implementaciones reales preservan moneda, redondeo y errores. Agregar una variante puede requerir cambios legítimos en selección o configuración, aunque no en los callers.
No ayuda si el comportamiento es único, la variación es trivial o el contrato necesita excepciones constantes. Tampoco resuelve por sí solo compatibilidad de interfaces, notificación entre interesados ni composición de responsabilidades.
Misuses y relaciones
Crear una Strategy por cada rama trivial o decenas de clases diminutas puede fragmentar una regla coherente. Multiplicar interfaces sin variación significativa desplaza comprensión hacia ensamblaje. Estrategias que inspeccionan internals del Context debilitan encapsulación; selección dispersa en todos los callers conserva el coupling original.
adapter-pattern atiende incompatibilidad de interfaces; no es el patrón apropiado solo porque deleguemos una llamada. decorator-pattern agrega responsabilidades alrededor de una capacidad compatible y puede envolver una estrategia cuando exista una presión adicional. observer-pattern organiza notificación a múltiples dependientes, no elección de algoritmo.
design-pattern recuerda que contexto, fuerzas y consecuencias forman parte de la identidad del patrón. design-patterns permite volver a las familias y comparar candidatos antes de convertir una posibilidad de composición en una obligación.