Implementando el Patrón Strategy de Forma Dinámica en C#
El Patrón de Diseño Strategy es uno de los patrones de comportamiento más fundamentales y útiles. Nos permite definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables. En esencia, permite que el algoritmo varíe independientemente de los clientes que lo utilizan.
A menudo, se introduce este patrón como una solución elegante al Principio de Abierto/Cerrado (OCP) de SOLID. Sin embargo, muchas implementaciones básicas se detienen a mitad de camino, dejando al cliente la responsabilidad de instanciar la estrategia correcta.
En este post, vamos a llevar el patrón Strategy al siguiente nivel. Partiremos de un ejemplo clásico y lo evolucionaremos hacia una solución dinámica y auto-configurable usando reflexión, haciendo nuestro sistema verdaderamente extensible y robusto.
El Escenario: Cálculo de Costos de Envío
Imaginemos que estamos construyendo un sistema de comercio electrónico. Una de las funcionalidades clave es calcular el costo de envío de un pedido. Los métodos de envío y sus costos pueden cambiar y expandirse con el tiempo.
El Enfoque Problemático: Una Larga Cadena de if-else
Un enfoque inicial y muy común sería tener una única clase ShippingCalculator
con un método que contenga una lógica condicional para cada tipo de envío.
1 | public class Order { /* ... */ } |
Este diseño es un claro ejemplo de una violación del Principio de Abierto/Cerrado. La clase está “abierta para modificación”, lo que significa que cada vez que el negocio introduce un nuevo método de envío, un desarrollador debe entrar y cambiar esta clase, arriesgándose a introducir errores en la lógica existente.
La Solución Clásica: El Patrón Strategy
El patrón Strategy nos ofrece una salida elegante. La idea es extraer cada algoritmo de cálculo en su propia clase, todas implementando una interfaz común.
Paso 1: Definir la Interfaz de la Estrategia
Primero, definimos un contrato que todas nuestras estrategias de cálculo de envío deben seguir.
1 | public interface IShippingStrategy |
Paso 2: Crear las Estrategias Concretas
A continuación, creamos una clase separada para cada método de envío.
1 | public class StandardShipping : IShippingStrategy |
Paso 3: Usar las Estrategias
Ahora, nuestra clase ShippingCalculator
ya no contiene la lógica condicional. En su lugar, recibe un objeto de estrategia y lo utiliza.
1 | public class ShippingCalculator |
¡Genial! Hemos cumplido con el Principio de Abierto/Cerrado. Para agregar un nuevo método de envío, simplemente creamos una nueva clase que implemente IShippingStrategy
. La clase ShippingCalculator
no necesita ser modificada.
Pero… ¿hemos resuelto el problema por completo? No del todo. Hemos trasladado la responsabilidad de la selección de la estrategia al código cliente. El cliente ahora necesita saber sobre todas las clases de estrategia concretas y contener la lógica para decidir cuál instanciar.
La Solución Avanzada: Una Fábrica de Estrategias Dinámica
Para que nuestro sistema sea verdaderamente desacoplado y extensible, necesitamos un mecanismo que pueda seleccionar la estrategia correcta de forma automática, basándose en algún dato (como un string
que identifique el método de envío).
Aquí es donde entra en juego el Patrón Factory, potenciado con un poco de reflexión de C#.
Paso 1: Identificar las Estrategias con Atributos
Para que nuestra fábrica pueda reconocer las estrategias, las marcaremos con un atributo personalizado que contenga su nombre único.
1 | // Un atributo para decorar nuestras clases de estrategia. |
Paso 2: Construir la Fábrica Dinámica
Esta fábrica será la pieza central de nuestra solución. Al iniciarse, escaneará nuestro ensamblado en busca de clases que implementen IShippingStrategy
y tengan nuestro atributo. Luego, las registrará en un diccionario para un acceso rápido y eficiente.
1 | using System.Reflection; |
Nota: En una aplicación real con Inyección de Dependencias, esta fábrica se registraría como un servicio Singleton para que el escaneo solo ocurra una vez al inicio de la aplicación.
Paso 3: Refinar el ShippingCalculator
y el Order
Ahora, el ShippingCalculator
usará la fábrica para obtener la estrategia correcta, basándose en una propiedad del objeto Order
.
1 | public class Order |
El Resultado: Un Sistema Verdaderamente Extensible
Veamos cómo se ve el uso final. El código cliente es ahora limpio, declarativo y completamente ajeno a las implementaciones concretas de las estrategias.
1 | // 1. Inicializamos nuestra fábrica (esto se haría una vez al inicio de la app). |
Conclusión
Al combinar el Patrón Strategy con un Patrón Factory dinámico, hemos creado un sistema que no solo cumple con el Principio de Abierto/Cerrado, sino que lo hace de una manera elegante y automatizada.
- Cerrado para Modificación: Las clases
ShippingCalculator
yShippingStrategyFactory
son estables y no necesitan cambios. - Abierto para Extensión: Agregar nueva funcionalidad es tan simple como crear una nueva clase.
Este enfoque avanzado del patrón Strategy es una herramienta poderosa para construir software flexible, mantenible y que puede evolucionar con las necesidades del negocio sin desmoronarse.