El Strategy es un patrón que encapsula algoritmos compatibles detrás de una interfaz común. El contexto puede recibir uno u otro y cambiar su comportamiento sin conocer sus detalles.
Todos hemos escrito alguna vez un método monstruoso lleno de condicionales. Imagina un sistema de descuentos en una tienda:
// ❌ El infierno de los condicionales
public double CalcularDescuento(string tipoCliente, double total)
{
if (tipoCliente == "Normal") {
return total;
}
else if (tipoCliente == "VIP") {
return total * 0.90;
}
else if (tipoCliente == "Empleado") {
return total * 0.50; // ¡Gran descuento!
}
else if (tipoCliente == "BlackFriday") {
// ... lógica compleja ...
}
// ... y así hasta el infinito
}¿El problema?
- Viola el principio Open/Closed: Cada vez que quieras añadir un nuevo tipo de descuento, tienes que abrir esta clase y modificarla, arriesgándote a romper lo que ya funcionaba.
- Es difícil de mantener: La clase crece sin control.
- Es rígido: El algoritmo está “tatuado” en la clase.
El patrón Strategy nos dice: “Define una familia de algoritmos, encapsula cada uno de ellos y hazlos intercambiables”.
En lugar de tener un solo método que lo hace todo, tenemos varias clases pequeñas (Estrategias) que hacen una sola cosa, y un objeto principal (Contexto) que usa una de ellas.
El Ejemplo: Un Navegador GPS
Imagina que estamos programando el GPS de Google Maps. El usuario selecciona un origen y un destino, y nosotros tenemos que calcular la ruta.
Pero la ruta no se calcula igual si vas en Coche, a Pie o en Bicicleta.
- El coche busca carreteras rápidas.
- El peatón puede usar parques y aceras.
- La bici busca carriles bici y evita autopistas.
La Solución con Strategy
En lugar de un switch(tipoTransporte), creamos una interfaz común IEstrategiaRuta. Luego, creamos una clase para cada algoritmo de ruta.
- Context (
NavegadorGPS): Es la clase que usa la estrategia. Mantiene una referencia a la interfaz. - Strategy (
IEstrategiaRuta): La interfaz común. - Concrete Strategies (
RutaCoche,RutaPie…): Las implementaciones específicas.
Implementación en C#
Vamos a ver cómo esto limpia nuestro código de una forma espectacular.
Definimos el contrato que todos los algoritmos deben cumplir.
public interface IEstrategiaRuta
{
void ConstruirRuta(string origen, string destino);
}Cada clase implementa su propia lógica. Son clases pequeñas, limpias y fáciles de testear.
public class RutaCoche : IEstrategiaRuta
{
public void ConstruirRuta(string origen, string destino)
{
Console.WriteLine($"🚗 Calculando ruta en COCHE de {origen} a {destino} por autopista.");
}
}
public class RutaPie : IEstrategiaRuta
{
public void ConstruirRuta(string origen, string destino)
{
Console.WriteLine($"🚶 Calculando ruta A PIE de {origen} a {destino} por zonas peatonales.");
}
}
public class RutaTransportePublico : IEstrategiaRuta
{
public void ConstruirRuta(string origen, string destino)
{
Console.WriteLine($"🚌 Calculando ruta en AUTOBÚS de {origen} a {destino} mirando horarios.");
}
}El navegador no sabe cómo se calcula la ruta. Solo sabe que tiene una estrategia y la ejecuta. Lo importante aquí es el método SetEstrategia, que permite cambiar el comportamiento en tiempo de ejecución.
public class NavegadorGPS
{
private IEstrategiaRuta _estrategia;
// Podemos asignar una estrategia inicial
public NavegadorGPS(IEstrategiaRuta estrategiaInicial)
{
_estrategia = estrategiaInicial;
}
// EL PUNTO CLAVE: Permitimos cambiar la estrategia "al vuelo"
public void SetEstrategia(IEstrategiaRuta nuevaEstrategia)
{
Console.WriteLine("--- Cambiando modo de transporte ---");
_estrategia = nuevaEstrategia;
}
public void EjecutarRuta(string A, string B)
{
if (_estrategia == null)
{
Console.WriteLine("⚠️ No se ha seleccionado transporte.");
return;
}
// Delegamos el trabajo a la estrategia actual
_estrategia.ConstruirRuta(A, B);
}
}class Program
{
static void Main(string[] args)
{
// 1. Iniciamos el GPS en modo Coche
var gps = new NavegadorGPS(new RutaCoche());
gps.EjecutarRuta("Madrid", "Barcelona");
// 2. El usuario aparca y quiere ir andando al hotel.
// CAMBIAMOS el algoritmo sin crear un objeto GPS nuevo.
gps.SetEstrategia(new RutaPie());
gps.EjecutarRuta("Parking", "Hotel");
// 3. Al día siguiente va en bus
gps.SetEstrategia(new RutaTransportePublico());
gps.EjecutarRuta("Hotel", "Museo");
}
}Salida:
🚗 Calculando ruta en COCHE de Madrid a Barcelona por autopista.
--- Cambiando modo de transporte ---
🚶 Calculando ruta A PIE de Parking a Hotel por zonas peatonales.
--- Cambiando modo de transporte ---
🚌 Calculando ruta en AUTOBÚS de Hotel a Museo mirando horarios.Ventajas del Strategy
- Elimina condicionales: Adiós al
switchgigante. El código es más legible. - Open/Closed Principle (OCP): ¿Quieres añadir
RutaBicicleta? Creas la clase y listo. No tocas niNavegadorGPSni las otras rutas. - Intercambio en caliente: Puedes cambiar el comportamiento del objeto mientras la aplicación está corriendo (como cuando un juego cambia la IA del enemigo de “Pasivo” a “Agresivo” si le disparas).
¿Cuándo usarlo?
- Cuando tienes muchas clases que solo se diferencian en la forma en que ejecutan cierta conducta.
- Cuando necesitas distintas variantes de un algoritmo.
- Cuando una clase tiene un condicional enorme que elige entre distintas ramas de ejecución.