patron-strategy

Patrón Strategy: intercambia algoritmos

  • 5 min

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
}
Copied!

¿El problema?

  1. 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.
  2. Es difícil de mantener: La clase crece sin control.
  3. 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);
}
Copied!

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.");
    }
}
Copied!

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);
    }
}
Copied!
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");
    }
}
Copied!

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.
Copied!

Ventajas del Strategy

  1. Elimina condicionales: Adiós al switch gigante. El código es más legible.
  2. Open/Closed Principle (OCP): ¿Quieres añadir RutaBicicleta? Creas la clase y listo. No tocas ni NavegadorGPS ni las otras rutas.
  3. 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.