Patrón Strategy
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 55 de 70.
El patrón Strategy es un patrón de comportamiento que te permite definir una familia de algoritmos, encapsular cada uno en su propia clase y hacerlos intercambiables en tiempo de ejecución. En lugar de definir el comportamiento de forma rígida (hardcoding), inyectas la estrategia deseada en un objeto.
El patrón consta de tres partes: una interfaz de estrategia que define el contrato del algoritmo, clases de estrategia concretas que implementan diferentes comportamientos y una clase de contexto que utiliza una estrategia:
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}El contexto (ShoppingCart) no sabe qué método de pago está utilizando; simplemente llama a Pay() en cualquier estrategia que se haya proporcionado:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Pagado 99.99 vía Tarjeta de Crédito
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Pagado 49.99 vía PayPalEl patrón Strategy elimina la lógica condicional compleja. Agregar un nuevo método de pago significa crear una nueva clase que implemente IPaymentStrategy - no se necesitan cambios en ShoppingCart. Esto hace que su código esté abierto a la extensión pero cerrado a la modificación.
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 algoritmos de precios, y su sistema debe permitir cambiar entre ellos en tiempo de ejecución sin cambiar la lógica central de procesamiento de pedidos.
Organizará su código en tres archivos:
ShippingStrategy.cs: Defina una interfazIShippingStrategyen el espacio de nombresShippingcon un métodoCalculateCost(double weight)que devuelva undouble. Luego cree tres clases de estrategia concretas:StandardShipping- calcula el costo comoweight * 1.5ExpressShipping- calcula el costo comoweight * 3.0 + 5.0(incluye una tarifa plana express)OvernightShipping- calcula el costo comoweight * 5.0 + 10.0(servicio premium con una tarifa base más alta)
OrderProcessor.cs: Cree una claseOrderProcessoren el mismo espacio de nombres. Esta es su clase de contexto que utiliza una estrategia de envío. Debe tener:- Un campo privado para mantener la
IShippingStrategyactual - Un método
SetShippingMethod(IShippingStrategy strategy)para cambiar la estrategia de envío - Un método
ProcessOrder(double weight)que utilice la estrategia actual para calcular y devolver el costo de envío
- Un campo privado para mantener la
Program.cs: Reúna todo creando unOrderProcessory demostrando cómo puede intercambiar estrategias de envío en tiempo de ejecución. El mismo procesador puede calcular costos utilizando diferentes algoritmos simplemente cambiando su estrategia.
Recibirá dos entradas:
- El tipo de método de envío:
standard,express, oovernight - El peso del paquete como un número decimal (por ejemplo,
4.5)
Cree un OrderProcessor, establezca la estrategia de envío adecuada según el tipo de entrada, luego procese el pedido e imprima el costo calculado.
Por ejemplo, si las entradas son express y 4.5, la salida debería ser:
18.5Esto demuestra el poder del patrón Strategy: su OrderProcessor no contiene ninguna lógica de cálculo de envío por sí mismo. Simplemente delega en cualquier estrategia que se le haya asignado, lo que facilita la adición de nuevos métodos de envío (como DroneDelivery) ¡sin modificar el procesador en absoluto!
Hoja de referencia
El patrón Strategy es un patrón de comportamiento que define una familia de algoritmos, encapsula cada uno en su propia clase y los hace intercambiables en tiempo de ejecución.
El patrón consta de tres partes:
- Interfaz Strategy: define el contrato del algoritmo
- Clases de estrategia concreta: implementan diferentes comportamientos
- Clase Contexto: utiliza una estrategia sin conocer sus detalles de implementación
Estructura de ejemplo:
// Interfaz Strategy
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
// Estrategias concretas
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
// Clase Contexto
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}Uso - cambiar estrategias en tiempo de ejecución:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Pagado 99.99 vía Credit Card
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Pagado 49.99 vía PayPalBeneficios:
- Elimina la lógica condicional compleja
- Abierto a la extensión, cerrado a la modificación
- Agregar nuevas estrategias solo requiere crear nuevas clases, sin cambios en el código existente
Pruébalo tú mismo
using System;
using Shipping;
class Program
{
public static void Main(string[] args)
{
// Leer la entrada
string shippingType = Console.ReadLine();
double weight = Convert.ToDouble(Console.ReadLine());
// TODO: Crear una instancia de OrderProcessor
// TODO: Basado en shippingType ("standard", "express", o "overnight"),
// crear la estrategia de envío apropiada y establecerla en el procesador
// TODO: Procesar el pedido e imprimir el costo calculado
}
}
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 externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes