Patrón Strategy
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 95 de 104.
El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Esto permite cambiar el comportamiento de un objeto en tiempo de ejecución sin modificar su código; el algoritmo varía independientemente de los clientes que lo utilizan.
El patrón consta de tres partes: una interfaz Strategy que declara el método del algoritmo, Estrategias Concretas que implementan diferentes variaciones, y un Contexto que utiliza una estrategia:
#include <iostream>
#include <memory>
// Interfaz de Estrategia
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Estrategias concretas
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Contexto
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}El ShoppingCart no sabe qué método de pago está utilizando; simplemente llama a pay() en cualquier estrategia que esté configurada. Puedes intercambiar estrategias en tiempo de ejecución con setPaymentMethod(), lo que hace que el sistema sea flexible y fácil de extender con nuevas opciones de pago.
Utiliza Strategy cuando tengas múltiples algoritmos para una tarea específica y quieras cambiar entre ellos dinámicamente, o cuando quieras evitar sentencias condicionales para seleccionar el comportamiento.
Desafío
FácilVamos a construir una Calculadora de Envío que utiliza el patrón Strategy para calcular los costos de entrega basados en diferentes métodos de envío. Este es un escenario práctico donde necesitas intercambiar algoritmos en tiempo de ejecución: el mismo paquete puede enviarse por tierra, aire o expreso, cada uno con su propia lógica de precios.
Organizarás tu código en tres archivos:
ShippingStrategy.h: Define tu interfaz de estrategia y las estrategias de envío concretas.Crea una clase abstracta
ShippingStrategycon un método virtual purocalculateCost(double weight)que devuelva el costo de envío como un double, junto con un destructor virtual.Luego implementa tres estrategias concretas:
GroundShipping— cuesta1.5por unidad de peso (weight * 1.5)AirShipping— cuesta4.0por unidad de peso (weight * 4.0)ExpressShipping— cuesta6.5por unidad de peso más una tarifa fija de10.0(weight * 6.5 + 10.0)
ShippingService.h: Crea la clase de contexto que utiliza una estrategia de envío.Tu clase
ShippingServicedebe contener unstd::unique_ptr<ShippingStrategy>como miembro privado. Implementa:- Un método
setStrategy(std::unique_ptr<ShippingStrategy> strategy)para cambiar el método de envío - Un método
calculateShipping(double weight)que utilice la estrategia actual para calcular y devolver el costo
Si no hay ninguna estrategia establecida cuando se llama a
calculateShipping, devuelve0.0.- Un método
main.cpp: Demuestra el intercambio de estrategias en tiempo de ejecución.Lee dos entradas:
- Peso del paquete (double)
- Método de envío:
ground,air, oexpress
Crea un
ShippingServicey establece la estrategia adecuada basada en el método de entrada. Calcula e imprime el costo de envío.Luego cambia a una estrategia diferente (usa
airsi la entrada no fueair, de lo contrario usaground) y calcula el costo nuevamente para el mismo peso. Esto demuestra el poder de intercambiar estrategias en tiempo de ejecución.Imprime cada costo en su propia línea con exactamente un decimal, precedido por el nombre del método:
[Method]: $[cost]
Por ejemplo, con las entradas 5.0 y ground:
Ground: $7.5
Air: $20.0Con las entradas 3.0 y express:
Express: $29.5
Air: $12.0Con las entradas 10.0 y air:
Air: $40.0
Ground: $15.0Observa cómo el ShippingService no necesita conocer los detalles de cada algoritmo de precios; simplemente delega en cualquier estrategia que esté establecida actualmente. Puedes añadir fácilmente nuevos métodos de envío (como entrega por dron o en el mismo día) sin modificar la clase de servicio en absoluto.
Hoja de referencia
El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Esto permite cambiar el comportamiento de un objeto en tiempo de ejecución sin modificar su código.
El patrón consta de tres partes:
- Interfaz Strategy: Una clase abstracta que declara el método del algoritmo
- Estrategias Concretas: Clases que implementan diferentes variaciones del algoritmo
- Contexto: Una clase que utiliza una estrategia y puede cambiar entre ellas
// Interfaz Strategy
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Estrategias concretas
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Contexto
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};El contexto no sabe qué estrategia concreta está utilizando; simplemente llama al método de la interfaz. Las estrategias se pueden intercambiar en tiempo de ejecución mediante un método setter:
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);Cuándo usarlo: Utilice Strategy cuando tenga múltiples algoritmos para una tarea específica y desee cambiar entre ellos dinámicamente, o cuando desee evitar sentencias condicionales para seleccionar el comportamiento.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"
int main() {
double weight;
std::string method;
std::cin >> weight;
std::cin >> method;
// Configura la salida a 1 decimal
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: Basado en el método de entrada ("ground", "air", o "express"):
// 1. Establece la estrategia apropiada en el servicio
// 2. Calcula e imprime el costo en el formato: "[Method]: $[cost]"
// TODO: Cambia a una estrategia diferente:
// - Si la entrada fue "air", cambia a GroundShipping
// - De lo contrario, cambia a AirShipping
// Calcula e imprime el nuevo costo
return 0;
}
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 externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados10Visión general de la STL
Visión general y filosofía de la STLContenedores de la STLIteradoresAlgoritmos de la STLFuntores y expresiones lambdaResumen - Frecuencia de palabras13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonFactory y Abstract FactoryPatrón BuilderPatrón ObserverPatrón Strategy2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico