Strategy

Define una familia de algoritmos, encapsulados cada uno, e intercambiables. Strategy hace que los algoritmos varíen independientemente del cliente que los esté usando.

¿Te suena conocido?, tal vez lo hayas usado ya, sin saberlo. A continuación un caso práctico a resolver con este patrón. Sugerencia: Antes de ver el código solución del patrón aplicado, intenta aplicar los principios de diseño que mencioné anteriormente.

Caso Práctico:
Joe trabaja para una compañía que hace videojuegos y se quiere un juego que muestra a las distintas especies de patos en el mundo simulando sus características. Como primer entendimiento con el cliente se acordó únicamente simular el sonido que emiten dichas aves con un "Quak" y que se muestren nadando.
¿Cómo lo hizo Joe? Con una clase general "Duck" que encapsule las características más generales de todos los patos, implementar los métodos quak() swim() y crear un método abstracto display() para mostrar gáficamente el ave y del cual van a hacer un override todas sus hijas, que representan a todos los patos que existen  y que heredan de Duck. Joe hace el demo con 2 clases de patos distintas con sus implementaciones propias de display y todos hacen "Quack" y nadan.

Al cliente le gustó su demo, de hecho quedó fascinado y quiere comprarlo y hacerlo en grande, lo que significa "cambios". El cliente quiere obviamente agregar más patos, y que ahora se muestren volando.
¿Cómo lo hizo Joe? Fácil, implementando un nuevo método a la clase Duck que se llame fly()  y ahora todos los patos van a volar ya que heredan de Duck. Caso resuelto, !Es un genio!. Hacen el release al cliente.

Al siguiente día la jefa de Joe le habla furiosa desde la junta de presentación del demo preguntándole porque había patitos de hule volando y haciendo quak en la pantalla, haciendo referencia a si eso era su idea de una broma, Joe dijo que no y entonces su jefa con tono severo le sugirió ponerse a trabajar.

¿Qué fue lo que hizo mal Joe? Joe utilizó herencia. Cuando Joe agregó un nuevo comportamiento a la Superclase Duck automáticamente sus hijas heredaron este comportamiento, cosa que no todos los patos pueden hacer.
¿Qué pensó Joe para corregirlo? Joe pensó en convertir los métodos fly() y quak() en abstractos para que las sublcases implementaran el comportamiento que quisieran, sin embargo en el caso del "Patito de hule" no debe haber una implementación del método fly ni quak, a lo cuál pensó Joe ¿Tendría caso tener un método vacío? y ¿Qué tal si existen más patos que no vuelen o no hagan quak?. Entonces se le ocurrió, y ¿Qué tal una Interfaz? Así si creo una interfaz Flyable y una Quackable con su método fly() y quak() correspondientes sólo las clases que ocupen volar o hacer quak las implementarían, ¡Muy buena idea!. Joe informó a su jefa, a lo que esta respondió: Esa es la idea más tonta que he recibido. Lo que estás proponiendo es "Duplicar código". Si pensabas que sobrescribir unos métodos era malo, ¿que harás cuando tengas que hacer una modificación a fly(), modificarás las más de 48 clases?.

Joe tiene un problema. ¿Que harías si fueras Joe? Ya vimos que la herencia no es la solución, y la interfaz sólo resolvería parte del problema pues rompe con el principio de "reutilizar código". Lo que haremos será aplicar el primer principio de diseño que vimos en el post anterior y separar lo que varía de lo que no lo hace. ¿Ya sabes que es? El "Comportamiento (fly y quak)". Ahora que lo sabes debes aislar este comportamiento y encapsularlo fuera del que no cambia, por lo que ahora tendremos un conjunto de comportamientos para volar y otro para hacer quak. Comportamientos como fly, not fly, quackear, squeakear, silece, etc. 


¿Y qué sigue? Ahora que tenemos separados los comportamientos lo que debemos hacer es definir una interfaz la cuál nos dará la presentación de los comportamientos a otras clases y los generalizará, ¿Te recuerda a algo esto?. Claro es el segundo principio que vimos en el post anterior. Con esto conseguimos flexibilidad ya que lo que queremos es agregarle comportamiento a las subclases de Duck y si le asignamos un comportamiento definido después no vamos a poder modificarlo en tiempo de ejecución.
¿Y cómo diseñamos una interfaz que pueda modificarse en tiempo de ejecución? Realmente diseñar una interfaz significa diseñar una superclase y sus subclases, así de esta manera podemos utilizar polimorfismo y hacer que instancia de la superclase pueda referirse a cualquiera de sus subclases, en tiempo de ejecución, por lo que haremos una interfaz FlyingBehavior y QuackingBehavior con sus subclases correspondientes.

Ya tenemos aislados nuestros comportamientos y ya hicimos una interfaz con ellos, ahora sólo queda aplicar el último de los principios que vimos en el post anterior e integrar los comportamientos con Duck. ¿Por que con Duck? No habrás pensado que cometeríamos el mismo error anterior, ya dijimos que el hecho de tener una interfaz y hacer que solo implementen de ella las clases que lo ocupen no es la solución y esto no favorecería la reutilización de código, si lo ponemos en Duck lo que las subclases van a heredar será un comportamiento no una implementación. ¿Entonces que haremos? Lo que haremos es hacer una composición en la que podamos decir que Duck tiene un comportamiento de volar y de hacer quack, la cual van a heredar las subclases como atributos, y puedan instanciarlo en su constructor con el comportamiento deseado (FlyWithWings, Quak, Squeak, etc.), pero además agregaremos un método performQuak() y uno performFly() en Duck para que en este método podamos mostrar la implementación de fly() o quak() del comportamiento elegido en las subclases.

Como se ve en la imagen en Duck no nos interesa que comportamiento sea, únicamente nos interesa que sepa volar o hacer quak. Ahora sólo nos falta agregar el último toque que describa por completo nuestro Patrón Strategy y es "Hacer lo que varía intercambiable", en nuestro caso hacer los comportamientos intercambiables, esto es que podamos intercambiar de tipos de comportamiento en tiempo de ejecución ya que como lo tenemos cada subclase al instanciarse tiene definido un comportamiento, y lo que queremos es que ese comportamiento pueda cambiar. ¿Y para que?, pues imaginemos que el cliente quiere que el pato muestre su ciclo de vida desde que nace hasta que se hace adulto, entonces un pato  no hace quack, ni vuela al nacer, pero en su adultez si lo hará, por lo que debemos hacer que en tiempo de ejecución su comportamiento pueda cambiar a que si pueda volar y hacer quack. Esto lo podemos resolver con un nuevo método setFlyBehavior(FlyingBehavior fb) y setQuackBehavior(QuackingBehavior qb) de tipo setter que nos permita asignar un nuevo comportamiento.
¡FELICIDADES! Ahora ya sabes cómo diseñar el Patrón Strategy y así es como el juego de patos de Joe se resolverá y tendrá éxito. Como premio te mostraremos cómo quedó el diseño y te mostraré el código (by @juanitodread) para que lo puedas ver, probar, extender, etc. ¡Disfrútalo!
Strategy Pattern in Duck Game of Joe. 

Y aquí el código en el repositoro de github de StrategyPattern.

¿Entendiste el patrón?, ¿Tienes dudas?, no olvides preguntar, comentar, sugerir o corregir algo si lo deseas, con gusto responderé a cualquiera de tus comentarios.
Espero les sea de utilidad y puedan aprender algo nuevo con esta serie de posts, esperen muy pronto el siguiente. ¡Saludos!

Categories

Seguidores

MarceStarlet. Con la tecnología de Blogger.
Powered By Blogger