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.
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.
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.
Este patrón es recomendable cuando:
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.
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.
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.
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.
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.
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
}
}
Los participantes que vimos antes son: Context, Strategy, ConcreteStrategy, Client:
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.
Los objetos ConcreteStrategy suelen ser buenos pesos ligeros.
Similares en estructura, State permite cambiar el comportamiento de acuerdo a su estado interno, Strategy permite cambiar el comportamiento del objeto dinámicamente.