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 permite definir una familia de algoritmos, encapsular cada uno en su propia clase y hacerlos intercambiables en tiempo de 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 de forma rígida la lógica de descuento con condicionales, defines cada descuento como una clase separada:
<?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 múltiples 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, sin tocar el código existente.
Desafío
FácilVamos a construir una calculadora de costos de envío utilizando el Patrón Strategy. Los diferentes métodos de envío tienen diferentes reglas de precios: el envío estándar cobra una tarifa plana, 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 plana 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 el contexto. Tu calculadora debe:- Aceptar una
ShippingStrategyen su constructor (usa promoción de 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 a la estrategia actual
- Aceptar una
main.php— Incluye los archivos ShippingMethods y ShippingCalculator. Recibirás dos entradas: un tipo de envío ("standard","express", o"overnight") y el peso de un paquete.Crea un
ShippingCalculatorcomenzando conStandardShipping. Basándote en la entrada del tipo de envío, usasetStrategy()para cambiar al método de envío apropiado. Luego calcula el costo para el peso dado e imprime el resultado formateado a dos decimales.La salida debe ser:
Shipping cost: $[cost]
Este desafío demuestra cómo el Patrón Strategy hace que tu sistema de envío sea flexible: agregar un nuevo método de envío como SameDayShipping solo requiere crear una nueva clase que implemente la interfaz, sin tocar la calculadora ni los métodos de envío existentes.
Hoja de referencia
El Patrón Strategy es un patrón de diseño de comportamiento que define una familia de algoritmos, encapsula cada uno en su propia clase y los hace intercambiables en tiempo de ejecución.
Define una interfaz para la estrategia:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}Implementa estrategias concretas:
<?php
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);
}
}Crea una clase de contexto que utilice la estrategia:
<?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);
}
}Utiliza el patrón inyectando estrategias:
<?php
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100); // 100
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100); // 80El Patrón Strategy permite cambiar entre algoritmos sin modificar la clase que los utiliza. Añadir nuevas estrategias solo requiere crear nuevas clases que implementen la interfaz.
Pruébalo tú mismo
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// Leer entrada
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: Crear un ShippingCalculator comenzando con StandardShipping
// TODO: Basado en el tipo de envío ingresado ("standard", "express", o "overnight"),
// usar setStrategy() para cambiar al método de envío apropiado
// TODO: Calcular el costo para el peso dado
// TODO: Imprimir el resultado formateado a dos decimales
// Formato de salida: 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 OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - 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 __unserializeResumen - 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)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy