Patrón Strategy
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy. Lección 74 de 91.
El patrón Strategy es un patrón de diseño de comportamiento que te permite definir una familia de algoritmos, encapsular cada uno en su propia clase y hacer que sean intercambiables durante la ejecución. El objeto que utiliza la estrategia no necesita saber qué algoritmo está ejecutando.
Imagina un carrito de compras que puede aplicar diferentes estrategias de descuento. En lugar de codificar directamente la lógica de descuento con condicionales, defines cada descuento como una clase independiente:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}
La clase de contexto acepta cualquier estrategia y delega el cálculo:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);
Salida:
100
80El patrón Strategy destaca cuando tienes varias formas de realizar una operación y quieres cambiar entre ellas sin modificar la clase que las utiliza. Añadir un nuevo tipo de descuento significa crear una nueva clase, no modificar el código existente.
Desafío
FácilConstruyamos una calculadora de costos de envío utilizando el patrón Strategy. Los diferentes métodos de envío tienen distintas reglas de precios: el envío estándar cobra una tarifa fija, el envío exprés añade un porcentaje y el envío nocturno combina ambos. El patrón Strategy te permite intercambiar estos cálculos sin cambiar el código que los utiliza.
Organizarás tu código en cuatro archivos:
ShippingStrategy.php: Crea una interfazShippingStrategyque defina el contrato para todos los métodos de envío. Debe tener un métodocalculate(float $weight): floatque reciba el peso del paquete y devuelva el costo de envío.ShippingMethods.php: Incluye el archivo ShippingStrategy y crea tres clases que implementen la interfaz:StandardShipping: Cobra una tarifa fija de$5.00independientemente del pesoExpressShipping: Cobra$2.00por kilogramo (multiplica el peso por 2)OvernightShipping: Cobra una tarifa base de$10.00más$3.00por kilogramo
ShippingCalculator.php: Incluye el archivo ShippingStrategy y crea una claseShippingCalculatorque actúe como contexto. Tu calculadora debe:- Aceptar un
ShippingStrategyen su constructor (utiliza la promoción del constructor) - Tener un método
setStrategy(ShippingStrategy $strategy): voidpara cambiar el método de envío en tiempo de ejecución - Tener un método
calculateCost(float $weight): floatque delegue en la estrategia actual
- Aceptar un
main.php: Incluye los archivos ShippingMethods y ShippingCalculator. Recibirás dos entradas: un tipo de envío ("standard","express"u"overnight") y el peso de un paquete.Crea un
ShippingCalculatorque comience conStandardShipping. Según la entrada del tipo de envío, utilizasetStrategy()para cambiar al método de envío adecuado. Después, calcula el costo para el peso indicado e imprime el resultado con dos decimales.La salida debe ser:
Shipping cost: $[cost]
Este desafío demuestra cómo el patrón Strategy hace flexible tu sistema de envíos: añadir un nuevo método de envío, como SameDayShipping, solo requiere crear una nueva clase que implemente la interfaz, sin modificar la calculadora ni los métodos de envío existentes.
Pruébalo tú mismo
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// Leer entrada
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: Crea un ShippingCalculator empezando con StandardShipping
// TODO: Basado en la entrada del tipo de envío ("standard", "express" o "overnight"),
// usa setStrategy() para cambiar al método de envío apropiado
// TODO: Calcula el costo para el peso dado
// TODO: Imprime el resultado formateado a dos decimales
// Output format: Shipping cost: $[cost]
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases frente a objetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Repaso: calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasRepaso - Jerarquía de empleados7Encapsulación
Público, protegido, privadoModificadores de acceso en profundidadGetters y settersOcultación de la informaciónPromoción del constructor (8.0)Repaso: sistema de registros de estudiantes10Conceptos avanzados de OOP
Composición vs. herenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibras (PHP 8.1)Análisis profundo de la clonación de objetosGeneradores e iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Namespaces y carga automática
Introducción a los NamespacesLa palabra clave useEstándar de carga automática PSR-4Autoloader de ComposerRepaso: proyecto organizado5Interfaces y contratos
Introducción a las interfacesImplementación de interfacesImplementación de múltiples interfacesInterfaces vs. clase abstractaTipado con interfacesRepaso - Calculadora de formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeRepaso: colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Repaso: gestor de cuentas bancarias6Polimorfismo
Repaso de la sobrescritura de métodosPolimorfismo mediante interfacesAnotaciones de tipo y tipos uniónEnlace estático tardíoRepaso: procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos entre TraitsMétodos abstractos en TraitsTraits frente a la herencia12Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de PHP online