Design Patterns - Strategy

Propósito

El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables.

Permite que el algoritmo varíe independientemente de los clientes que lo usan.

Problema

El patrón Strategy trata los siguientes problemas:

Acoplamiento rígido: Sin el patrón Strategy, una clase puede tener que incluir varios comportamientos (algoritmos) y seleccionar entre ellos usando condicionales. Esto hace que la clase sea difícil de mantener y ampliar.

Incapacidad para cambiar los algoritmos dinámicamente en tiempo de ejecución: Escenarios donde el comportamiento de una clase debe cambiar según el contexto pueden ser difíciles de implementar.

Solución

El patrón Strategy propone las siguientes soluciones:

Encapsulamiento de Algoritmos: Cada algoritmo o comportamiento se encapsula en su propia clase, conocida como una estrategia concreta. Esto facilita la sustitución de un algoritmo por otro.

Delegación de Responsabilidades: La clase principal, o contexto, delega la responsabilidad de ejecutar un comportamiento a la estrategia concreta, en lugar de implementar directamente el comportamiento.

Estructura

strategy

Participantes

  • Context: Mantiene una referencia a una Strategy. Puede definir una interfaz que le permita al Strategy acceder a sus datos.
  • Strategy (Interface): Interfaz común para todas las estrategias con al menos un método para ser implementado.
  • ConcreteStrategy: Implementaciones específicas de la interfaz de Strategy.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • hay varias formas de hacer algo y se quiere elegir la manera de hacerlo en tiempo de ejecución.
  • se quieren evitar estructuras condicionales complejas y se prefiere la delegación a la herencia.
  • se tienen clases que difieren solo en su comportamiento.

Ventajas

Separación de Responsabilidades: Cada estrategia encapsula un comportamiento específico, manteniendo las clases compactas y enfocadas.

Flexibilidad en Tiempo de Ejecución: Permite cambiar algoritmos en tiempo de ejecución.

Facilita la Extensión: Permite agregar nuevas estrategias sin cambiar el contexto.

Desventajas

Complejidad: Agregar clases y objetos adicionales, lo que puede complicar el diseño.

Dificultad de Uso: Los clientes necesitan entender las diferencias entre las estrategias para poder seleccionar la adecuada.

Objetos Adicionales: Puede aumentar el número de objetos en el sistema.

Ejemplo: Sistema de Navegación con Estrategias de Rutas Variables

Debemos crear un sistema de navegación para vehículos, que ofrece a los conductores la capacidad de seleccionar la mejor ruta basada en sus preferencias actuales y condiciones de viaje.

Los conductores pueden preferir la ruta más rápida para llegar a tiempo, la más corta para ahorrar combustible, o la más escénica para disfrutar del viaje.

Problema

El desafío está en implementar un sistema flexible que pueda adaptarse a las diferentes preferencias y necesidades de los conductores, sin sobrecargar el código con múltiples condicionales y estructuras complejas.

Además, queremos asegurarnos de que nuestro sistema pueda expandirse fácilmente en el futuro para incluir nuevas estrategias de ruta, sin necesidad de cambios significativos.

Solución planteada

Elegimos usar el patrón Strategy porque permite encapsular los diferentes algoritmos de ruta en clases separadas y hacerlos intercambiables dentro del contexto de nuestro sistema de navegación.

Esto no solo simplifica la estructura del código al separar las responsabilidades, sino que también brinda una manera fácil de agregar o modificar estrategias en el futuro.

Al delegar la responsabilidad de calcular la ruta a las clases de estrategia, mantenemos nuestro sistema de navegación flexible y fácil de mantener.

Definimos la interfaz RouteStrategy para armar la ruta entre dos puntos. Creamos cada estrategia concreta, ruta rápida, más corta y con un buen panorama. El sistema de navegación (Context) usa la estrategia indicada para navegar la ruta.

strategy

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la interfaz Strategy:


              
  interface RouteStrategy {
      void buildRoute(String start, String end);
  }
              
              

Creamos las concrete Strategies:


              
  class FastestRoute implements RouteStrategy {
      public void buildRoute(String start, String end) {
          System.out.println("Calculando la ruta más rápida de " + start + " a " + end);
      }
  }
  
  class ShortestRoute implements RouteStrategy {
      public void buildRoute(String start, String end) {
          System.out.println("Calculando la ruta más corta de " + start + " a " + end);
      }
  }
  
  class ScenicRoute implements RouteStrategy {
      public void buildRoute(String start, String end) {
          System.out.println("Calculando la ruta más escénica de " + start + " a " + end);
      }
  }
              
              

Implementamos el Context:


              
  class NavigationSystem {
      private RouteStrategy strategy;
  
      public NavigationSystem(RouteStrategy strategy) {
          this.strategy = strategy;
      }
  
      public void setStrategy(RouteStrategy strategy) {
          this.strategy = strategy;
      }
  
      public void navigate(String start, String end) {
          strategy.buildRoute(start, end);
      }
  }
              
              

El código cliente:


              
  public class VehicleDriver {
      public static void main(String[] args) {
          NavigationSystem navigation = new NavigationSystem(new FastestRoute());
  
          navigation.navigate("Casa", "Trabajo");  // Usa la ruta más rápida
          navigation.setStrategy(new ScenicRoute());
          navigation.navigate("Casa", "Parque");  // Cambia a la ruta más escénica
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Context, Strategy, ConcreteStrategy, Client:

  • RouteStrategy (Strategy): Interfaz común para las estrategias de ruta.
  • FastestRoute, ShortestRoute, ScenicRoute (ConcreteStrategy): Implementaciones específicas de estrategias de ruta.
  • NavigationSystem (Context): Usa una estrategia para construir la ruta.

Conclusiones

En este ejemplo vimos el uso del patrón Strategy en el sistema de navegación para vehículos.

Al encapsular los algoritmos de ruta en estrategias intercambiables, conseguimos un diseño que puede adaptarse fácilmente a las preferencias del conductor y puede ampliarse para incluir nuevas formas de cálculo de rutas.

A pesar del aumento en el número de clases, la claridad, la modularidad y la capacidad de mantenimiento del código han mejorado significativamente, preparando el sistema para futuras mejoras y nuevas características.

Patrones relacionados

  • Flyweight

Los objetos ConcreteStrategy suelen ser buenos pesos ligeros.

  • State

Similares en estructura, State permite cambiar el comportamiento de acuerdo a su estado interno, Strategy permite cambiar el comportamiento del objeto dinámicamente.