¿Alguna vez te has encontrado con un bloque de código que parece crecer sin control, lleno de sentencias `if-else if` o `switch` anidadas que intentan manejar diferentes variantes de una misma operación? Si la respuesta es sí, no estás solo. Es una situación común en el desarrollo de software y, a menudo, la señal de que tu aplicación podría beneficiarse de un diseño más flexible. La rigidez de este tipo de código no solo dificulta su lectura y mantenimiento, sino que también lo hace propenso a errores y extremadamente complicado de extender cuando surgen nuevos requisitos. Aquí es donde los patrones de diseño, y en particular el patrón Strategy, brillan con luz propia.
Los patrones de diseño son soluciones probadas a problemas comunes que surgen en el diseño de software. No son bibliotecas ni frameworks, sino guías conceptuales que nos ayudan a estructurar nuestro código de una manera más robusta, escalable y mantenible. En este tutorial, nos sumergiremos en el patrón Strategy, un patrón de comportamiento que nos permite definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables. Esto significa que podemos seleccionar un algoritmo en tiempo de ejecución, lo que nos da una flexibilidad increíble sin tener que modificar el código existente. Prepárate para descubrir cómo PHP puede ser tu aliado perfecto para implementar esta poderosa herramienta y cómo puedes empezar a aplicarla en tus proyectos hoy mismo, haciendo tu código más limpio, legible y adaptable al cambio.
¿Qué es el patrón Strategy?

El patrón Strategy es un patrón de diseño de comportamiento que define una familia de algoritmos, los encapsula y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan. En esencia, este patrón te permite cambiar el comportamiento de una clase en tiempo de ejecución. Imagina que tienes una clase que realiza una operación, pero esa operación puede ejecutarse de diferentes maneras, o siguiendo diferentes "estrategias". En lugar de codificar todas esas variantes dentro de la misma clase (lo que resultaría en un código monolítico y difícil de mantener), el patrón Strategy nos sugiere delegar esas variantes a clases separadas, cada una representando una estrategia específica.
La principal motivación detrás del Strategy es el principio "Open/Closed Principle" (OCP) de SOLID, que establece que las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para extensión, pero cerradas para modificación. Cuando necesitamos añadir una nueva estrategia, no deberíamos tener que modificar el código existente del cliente o del contexto que utiliza la estrategia. En cambio, simplemente añadiríamos una nueva clase de estrategia. Esto, en mi experiencia, es uno de los mayores beneficios, ya que minimiza el riesgo de introducir nuevos errores en código ya probado.
Consideremos un ejemplo cotidiano: viajar. Puedes ir al trabajo en coche, en autobús, en bicicleta o andando. Cada uno de estos métodos de transporte es una "estrategia" para llegar a tu destino. La forma en que llegas es independiente de ti (el "contexto") y puedes cambiar tu estrategia de transporte en cualquier momento sin cambiar quién eres. Si aparece un nuevo método de transporte, como un monopatín eléctrico, no necesitas cambiar tu propia esencia para usarlo; simplemente lo eliges como una nueva estrategia. Esta es la esencia del patrón Strategy.
Componentes clave del patrón Strategy
Para entender cómo funciona el patrón Strategy, es fundamental conocer los tres componentes principales que lo conforman:
La interfaz Strategy
Este es el contrato que todas las estrategias concretas deben implementar. Define un método (o varios) que todas las estrategias deben tener, asegurando que el contexto pueda interactuar con cualquier estrategia de la misma manera, sin preocuparse por los detalles internos de cada una. En PHP, esto se logra mediante una interfaz (`interface`). La interfaz Strategy es el pegamento que permite la intercambiabilidad y el polimorfismo, facilitando que el contexto pueda trabajar con cualquier implementación concreta.
Estrategias concretas (Concrete Strategies)
Estas son las implementaciones específicas de la interfaz Strategy. Cada clase concreta representa un algoritmo o comportamiento particular. Por ejemplo, si nuestra interfaz Strategy es `MetodoPago`, tendríamos clases como `TarjetaCreditoStrategy`, `PaypalStrategy` o `TransferenciaBancariaStrategy`, cada una implementando el método `procesarPago()` de una manera única. Aquí es donde reside la lógica específica de cada variante del algoritmo.
El Contexto (Context)
El Contexto es la clase que utiliza la Strategy. Mantiene una referencia a un objeto Strategy y delega la ejecución del algoritmo a ese objeto. El Contexto no sabe qué estrategia concreta está utilizando; solo sabe que está interactuando con un objeto que implementa la interfaz Strategy. Esto desacopla al Contexto de los detalles específicos de los algoritmos y permite que el Contexto cambie su comportamiento simplemente cambiando la estrategia que se le ha asignado. Es la clase cliente la que generalmente configura la estrategia en el Contexto.
Para una visión más general de los patrones de diseño y dónde encaja Strategy, puedes consultar recursos como la sección de patrones de comportamiento en
Refactoring.Guru.
Un ejemplo práctico en PHP: gestión de pagos
Para ilustrar el poder del patrón Strategy, vamos a desarrollar un ejemplo práctico: un sistema de procesamiento de pagos. Imaginemos que nuestra aplicación necesita manejar diferentes métodos de pago (tarjeta de crédito, PayPal, transferencia bancaria, etc.) y queremos que sea fácil añadir nuevos métodos sin alterar el código principal del procesamiento.
El problema inicial: código acoplado
Sin el patrón Strategy, nuestro código podría terminar luciendo algo como esto:
```php
class ProcesadorPagosAcoplado {
public function procesarPago(string $metodo, float $cantidad): bool {
if ($metodo === 'tarjeta_credito') {
// Lógica compleja para procesar tarjeta de crédito
echo "Procesando pago con tarjeta de crédito por " . $cantidad . "€\n";
// ... (validación de tarjeta, comunicación con pasarela, etc.)
return true;
} elseif ($metodo === 'paypal') {
// Lógica compleja para procesar PayPal
echo "Procesando pago con PayPal por " . $cantidad . "€\n";
// ... (API de PayPal, redirecciones, etc.)
return true;
} elseif ($metodo === 'transferencia_bancaria') {
// Lógica compleja para procesar transferencia bancaria
echo "Procesando pago con transferencia bancaria por " . $cantidad . "€\n";
// ... (generación de IBAN, instrucciones, etc.)
return true;
} else {
echo "Método de pago no soportado.\n";
return false;
}
}
}
// Uso
$procesadorAcoplado = new ProcesadorPagosAcoplado();
$procesadorAcoplado->procesarPago('tarjeta_credito', 100.50);
$procesadorAcoplado->procesarPago('paypal', 50.00);
// Cuando necesitemos añadir un nuevo método, tendremos que modificar esta clase.
```
Este código funciona, pero tiene varios problemas:
* **Acoplamiento alto:** La clase `ProcesadorPagosAcoplado` está fuertemente acoplada a los detalles de cada método de pago.
* **Violación del OCP:** Para añadir un nuevo método de pago (por ejemplo, Stripe o Apple Pay), tendríamos que modificar la clase `ProcesadorPagosAcoplado`, lo que podría introducir errores en la lógica existente.
* **Código difícil de leer y mantener:** Las múltiples sentencias `if-else if` hacen que la clase sea larga y compleja.
* **Violación del SRP:** La clase `ProcesadorPagosAcoplado` tiene múltiples razones para cambiar (cambios en un método de pago, adición de uno nuevo), lo que viola el Principio de Responsabilidad Única (
SRP).
Diseñando la solución con Strategy
Ahora, veamos cómo el patrón Strategy resuelve estos problemas.
Paso 1: la interfaz Strategy
Primero, definimos una interfaz `MetodoPago` que todas nuestras estrategias de pago deben implementar. Esto asegura que cada estrategia tendrá un método `pagar()`.
```php