Este DP es uno de los primeros que aprendí, por lo que le tengo especial "afecto". Además ilustra muy bien algunos de los principios de los DPs.
Primero que nada, la definición formal del Strategy Pattern es:
"Un patrón de diseño que permite definir una familia de algoritmos, encapsulando cada uno y haciéndolos intercambiables, de tal manera que permita que puedan variar en forma independiente del cliente que los usa"
Impresionante ¿Verdad? sin embargo, es más sencillo de lo que parece. Veamos ahora un diagrama UML que ilustra las partes principales de un Strategy Pattern y cómo se relacionan entre ellas:
![]() |
| Strategy Pattern |
Como se puede observar en el diagrama. La clase Context es nuestro "cliente", el cual hace referencia mediante composición a Strategy la cual puede ser ya sea una interfaz o una clase abstracta. A su vez, a partir de la clase Strategy se pueden desprender N clases (ConcreteStrategyA, ConcreteStrategyB...) las cuales implementarán el algoritmo cada una a su manera. La clase Strategy junto con sus clases derivadas es lo que forma la "familia de algoritmos", y son intercambiables porque para nuestro cliente será indistinto cual clase es la que está instanciada puesto que no las usa directamente, sino a través de Strategy. De esto también se deduce que los algoritmos pueden variar en forma independiente del cliente que los usa porque si agregamos una nueva clase "ConcreteStrategyZ" a la familia, o modificamos el código interno del "Algoritmo" de alguna de las clases ya existentes, nuestro cliente no sufre modificación alguna. Esto ilustra 3 principios básicos en los Design Patterns:
- Encapsula lo que varía.- Este principio nos indica que debemos identificar aquel código que sea susceptible de variar en nuestra clase y separarlo y encapsularlo, de tal manera que cuando varíe no afecte las demás partes.
- Favorece la composición sobre la herencia.- Es decir, en la medida de lo posible, incorpora funcionalidad nueva a tu clase usando composición y no herencia. Es verdad que la herencia es parte fundamental de la POO y que es imprescindible para la reutilización y especialización de las clases. Pero la composición te permite una mayor flexibilidad y hace un código más fácil de mantener. Más adelante pongo un ejemplo al respecto.
- Programa hacia una interfaz, no hacia una implementación.- Este principio se refiere a que en la medida de lo posible, cuando vayas a utilizar variables que hagan referencia a una clase, trata de que el tipo de la variable sea de la interfaz o clase base de la cual se deriva la clase que vas a usar, de esa manera, permites que el diseño sea flexible y la variable pueda contener cualquier clase derivada sin afectar partes del código que sean dependientes. Este principio es el que permite al Strategy Pattern crear su "familia de algoritmos intercambiables".
Para explicar su utilidad y en donde se puede aplicar este DP, partiremos de un ejemplo bastante trivial. Supongamos que deseamos tener una pequeña aplicación que se encarga de calcular la nómina de los trabajadores. Los factores para realizar el cálculo serían:
- Tipo de trabajador (Practicante, sindicalizado, administrativo)
- Categoría (General, Especializado, Experto)
- Deducciones especiales(Cuota Sindical, Fondo de Ahorro, Deducción Préstamo)
Para que el usuario pueda realizar las combinaciones que desee, el programa tendrá la siguiente interfaz:
Ahora, como podemos deducir de aquí, al presionar el botón "Calcular" el programa debe mostrar el total basándose en las opciones seleccionadas de cada grupo de opciones. Es aquí donde vamos a aprovechar el Strategy Pattern, de hecho, vamos a utilizar 3 familias de algoritmos, para ilustrar cómo podemos tener más de una sirviendo a una misma clase como cliente. Vamos a tener una estructura como la del siguiente diagrama UML:
![]() |
| Cálculo de Nómina usando Strategy Pattern |
Identificando las piezas en este diagrama, vemos que el Context o cliente es la clase "Nomina" y que mediante composición hace uso de 3 clases abstractas, que fungen como nuestros Strategies y cada uno de ellos es la base de una de las "familias de algoritmos". La primera, encargada de obtener el sueldo base dependiendo del tipo de trabajador, la segunda, de obtener un porcentade de incremento en base a la categoría y la tercera, obtiene un valor dependiendo de la deducción seleccionada.
Ahora vamos a ver cómo quedarían las clases codificadas en Lazarus/FreePascal, para simplificar solo voy a mostrar la parte de las declaraciones de las clases, sin incluír la implementación, de todas maneras al final de este post dejo un link para que puedas bajar el programa completo.
Primero la familia de algoritmos de TipoTrab:
Ahora veamos la familia de CategoriaTrab:
Y por último la familia de DeducEsp:
Ahora mostramos como quedaría la clase Nomina:
Como puedes ver, un solo cliente puede utilizar tantos Strategies como necesite, además, al ser usados mediante composición, y no mediante herencia, se pueden intercambiar los miembros de una familia de algoritmos en tiempo de ejecución, por lo que el comportamiento de nuestra clase cliente puede variar durante la vida del programa.
Seguro que has de pensar que no eran necesarios tantos problemas para un ejemplo tan sencillo, pero es solo ilustrativo. Imagina que tuvieras un sistema tipo ERP que se le vendiera a múltiples clientes y que entre los modulos que utiliza estuvieran los de contabilidad, Recursos Humanos y Administración. Utilizando Strategy Patterns para cada módulo, no habría necesidad de tocar el sistema base cuando necesitaras adaptar alguno de los modulos para que funcionara de acuerdo a las reglas de negocio de un cliente específico, y eso es solo un ejemplo.
El ejemplo mostrado tiene algunos "oportunidades de mejora" intencionales... ¿Podrás encontrarlas?, te voy a dar algunas pistas:
* ¿Cómo podría hacerle si un trabajador no tiene ninguna deducción especial?
* ¿Y si quisiera que tuviera más de una deducción...?
* Y hay algunas otras...
En resumen, el Strategy Pattern es útil cuando necesitemos de un DP que nos facilite construir una clase con áreas de ejecución que puedan variar incluso en tiempo de ejecución del programa y que nos facilite el mantenimiento.
El programa de ejemplo en Lazarus/FreePascal lo pueden bajar desde aquí.


No hay comentarios.:
Publicar un comentario