Patrones de Diseño en Java - Patrón Strategy

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!

15 comentarios

  1. Muy buena explicación mil gracias. Tengo una pregunta. Suponiendo que tenga una clase SuperHeroe, y una serie de SuperPoderes fija, es decir (volar, super fuerza, velocidad..... etc). Entonces al crear un objeto SuperHéroe, podría agregarle una, dos o más superPoderes... ¿Qué patrón de diseño podría utilizar? dado que con Interfaces no serviría sino para un sólo poder, al igual que con el patrón Strategy. Agradecería infinitamente la ayuda.

    ResponderEliminar
  2. La interfaz no es la que hace que tus objetos solo puedan tener un solo comportamiento diferente a la vez, al contrario la interfaz hace que tu puedas variar esos comportamientos y si quisieras más de un comportamiento a la vez lo que necesitas cambiar en tu clase SuperHeroe es en vez de hacer una referencia a tu Interfaz SuperPoderes sería crear una referencia a una lista de SuperPoderes para así poder decir que un SuperHeroe tiene distintos super poderes, algo así:

    List superPoderes = new ArrayList();

    donde indica que es una lista de tipo SuperPoderes (que a su vez es un "comportamiento" de un super heroe).

    Este patron Strategy te permite poder cambiar entre algoritmos en caso de que tu SuperHeroe tuviera un algoritmo para SuperPoderes y de algún otro como SuperTrajes, o en su defecto únicamente SuperPoderes, aunque de esta forma no explotarías por completo el patrón.

    Por otro lado aunque no variaras los algoritmos estarías usando principios de POO enfocados al buen diseño y eso ya es una gran ventaja. Recuerda que no todo es un patrón, y siempre dependerá de lo que necesites hacer. Usar principios de programación OO siempre será mejor que no usarlos.

    Espero te sirva, cualquier comentario que tengas, duda, sugerencia o reclamo aquí es bienvenido.

    NOTA: ¿Podrías usar algún otro patrón de diseño? Sí, dependiendo de que más desees agregar a tu app, así como lo comentas para mi es suficiente Strategy y los Principios OO que ya están aplicados.

    ResponderEliminar
  3. Mil gracias por tomarte el tiempo de responder.. en verdad te lo agradezco inmensamente. Pues en este caso pensé que podría ser un patrón de diseño... por la web vi uno.. con Strategy pero también serviría solo para agregar un sólo súperhéroe. Pero por ejemplo lo que varía es que si creo a Batman, no tendría Super Poderes, y si agrego a la Mujer Maravilla tendría varios... entonces por eso creí que podría ser un patrón. Por ejemplo, en este caso sólo serviría para un súper poder. http://blackout360.files.wordpress.com/2008/04/diagrama-de-clases1.png

    ResponderEliminar
  4. ¡Oh vaya! Claro ahora entiendo tu punto. Y lo que estás haciendo en sí si es un patrón (Strategy) porque según la definición estas encapsulando algoritmos en este caso poderes (Volar, VisionCalorifica,Velocidad, etc.) y los estás haciendo intercambiables al crear una interfaz iSuperPoderes mediante polimorfismo puedes intercambiar los poderes, ya que puedes hacer que la referencia a iSuperPoderes cambie entre los distintos poderes que tienes. Lo único que agregamos fue hacer que un SuperHeroe pudiera tener varios iSuperPoderes a la vez, creando una Lista.

    Lo que mencionas de que puedes hacer variar los poderes en el caso de Batman que no los tiene y la mujer maravilla sí, es muy acertado tu comentario y eso es justo lo que lo hace un patrón.

    Una buena sugerencia (por si aún no lo haz hecho) sería que al momento de crear un objeto de tipo SuperHeroe en su constructor puedes llenar la lista de iSuperPoderes que creaste y así al ser creado tendrá los super poderes que desees para cada super héroe.

    Esto posiblemente te lleve a otro patrón de diseño que es el Factory Design Pattern (dependerá de lo que desees hacer a futuro) y no es por promocionar pero es el siguiente patrón que estaré agregando en esta serie de posts.

    Saludos, y espero que te sirva.

    ResponderEliminar
  5. Muchisimas gracias.. eres una dura!!!... Pues te cuento.. lo que hice hasta ahora y lo implementé fue lo siguiente:
    Una clase abstracta llamada SuperPoder con un atributo nombrePoder. De ahí, extendí varias clases, dependiendo el superPoder, por ejemplo: Volar, SuperFuerza. Luego hice una Relación de 1 a N entre las clases SuperPoder y Superheroe Así:
    public abstract class SuperPoder {
    protected String poder;

    public SuperPoder(String nombre){
    poder=nombre;
    }
    public abstract void activarPoder();
    public abstract String getSuperPoder();
    }
    ---------------------------------------------------
    public class SuperFuerza extends SuperPoder{

    public SuperFuerza(String n){
    super(n);
    }

    @Override
    public void activarPoder() {
    System.out.println("Poder de fuerza sobrehumana");
    }

    @Override
    public String getSuperPoder() {
    return super.poder;
    }

    }
    ---------------
    public class Volar extends SuperPoder{

    public Volar(String n){
    super(n);
    }
    @Override
    public void activarPoder() {
    System.out.println("Poder para volar");
    }

    @Override
    public String getSuperPoder() {
    return super.poder;
    }

    }
    ----------------
    public class SuperHeroe {
    private String nombre;
    private LinkedList poderes;

    public SuperHeroe(){
    nombre="Mujer Maravilla";
    poderes= new LinkedList<>();
    }

    public void agregarPoder(SuperPoder poder){
    poderes.add(poder);
    }

    public void getPoderes(){

    Iterator it= poderes.iterator();
    while (it.hasNext()){
    SuperPoder sp=it.next();
    System.out.println(sp.getSuperPoder()+ "");
    }
    }
    }

    No sé si ésto cumpliría con un buen desarrollo OO, o definitivamente se requiere un patrón de diseño... Seguiré tu otro tema del siguiente patrón.. Súper interesante.

    Agradecidísimo contigo.

    ResponderEliminar
  6. Ok me parece un buen comienzo. Aquí unas observaciones:

    -Para mi creo que es mejor que dejes la clase abstracta SuperPoder como una interface y así quitar el atributo "poder" a mi parecer es redundante ya que el nombre de la clase ya indica que poder es, en vez de eso en el método getSuperPoder() retorna un string con el nombre del super poder.

    public interface ISuperPoder{
    public void activarPoder();
    public String getSuperPoder();
    }

    public class SuperFuerza implements ISuperPoder{
    @Override
    public void activarPoder() {
    System.out.println("Poder de fuerza sobrehumana");
    }

    @Override
    public String getSuperPoder() {
    return "Super Fuerza";
    }
    }

    -En la clase SuperHeroe en el atributo poderes, es mejor definir con el operador diamante <> de qué tipo será tu colección, en este caso de ISuperPoder. Además siempre es mejor crear una referencia al tipo padre List que es una interface que a sus implementaciones. Recuerda crear interfaces no implementaciones.

    private List poderes;
    ...
    poderes = new LinkedList();

    -En el método getPoderes puedes iterar sobre la lista de super poderes con un foreach en vez de un iterador ya que solo mostrararás datos. Con esto obtienes un código más legible y un método más sencillo.

    for(ISuperPoder sp: poderes){
    System.out.println(sp.getSuperPoder()+ "");
    }

    -Debes crear un método activarPoder(String poder) en SuperHeroe para activar el poder que desees de la lista de poderes que tiene el super heroe.
    -Debes tener clases que hereden de SuperHeroe para crear a tus propios super heroes y evitar ponerles nombre en el constructor y así agregar métodos getters y setters para cada atributo que tengas en la clase padre, en este caso según tu ejemplo la mujer maravilla:

    public class SuperHeroe {
    .
    .
    public SuperHeroe(){
    poderes= new LinkedList();
    }

    public void activarPoder(String nombrePoder){
    for(ISuperPoder poder : poderes){
    if(poder.getNombre.equals(nombrePoder)){
    poder.activarPoder(); //Este metodo es del poder no del SuperHeroe
    }
    }
    }

    public String getNombre(){
    return nombre;
    }

    public void setNombre(String nombre){
    this.nombre = nombre;
    }
    ....
    }

    public class MujerMaravilla extends SuperHeroe{

    public MujerMaravilla(){
    this.setNombre("La Mujer Maravilla");
    }
    }


    -Por último crea una clase Main en la cuál crearas super heroes con sus super poderes:

    public class Main{

    public static void main(String[] args){
    SuperHeroe sh = new MujerMaravilla();
    SuperPoder sp = new SuperFuerza();
    sh.agregarPoder(sp);
    sp = new Volar();
    sh.agregarPoder(sp);

    System.out.println("Nombre: " sh.getNombre + \n + "Super Poderes: " + sh.getPoderes());
    }
    }


    Espero te sirva la información, revisa con detalle cada paso y si tienes dudas con algo avisame.

    ResponderEliminar
  7. Te agradezco inmensamente tu ayuda. Si te envié el primer borrador que había hecho estableciendo en el constructor el nombre de la Mujer Maravilla.. por probar... Pues el otro funciona.. pero implementaré las interfaces y te diré cómo me va. El anterior código más funcional quedaba así:

    public abstract class SuperPoder {
    protected String poder;

    public SuperPoder(String nombre){
    poder=nombre;
    }
    public abstract void activarPoder();
    public abstract String getSuperPoder();
    }
    -------------------------------------------
    public class SuperFuerza extends SuperPoder{

    public SuperFuerza(String n){
    super(n);
    }

    @Override
    public void activarPoder() {
    System.out.println("Poder de fuerza sobrehumana");
    }

    @Override
    public String getSuperPoder() {
    return super.poder;
    }
    }
    ---------------------------------------------------------
    public class Volar extends SuperPoder{

    public Volar(String n){
    super(n);
    }
    @Override
    public void activarPoder() {
    System.out.println("Poder para volar");
    }

    @Override
    public String getSuperPoder() {
    return super.poder;
    }
    }
    --------------------------------------------
    public class SuperHeroe {
    private String nombre;
    private LinkedList poderes;


    public SuperHeroe(){
    nombre="";
    poderes= new LinkedList<>();
    }

    public void setNombre(String nombre){
    this.nombre=nombre;
    }

    public void agregarPoder(SuperPoder poder){
    poderes.add(poder);
    }

    public void getPoderes(){
    Iterator it= poderes.iterator();
    while (it.hasNext()){
    SuperPoder sp=it.next();
    System.out.println(sp.getSuperPoder()+ "");
    }
    }

    public String getNombre(){
    return nombre;
    }
    }
    ------------------------------------------------
    Y EL PRINCIPAL:
    public static void main(String[] args) {
    // TODO code application logic here
    SuperHeroe ww= new SuperHeroe();
    ww.setNombre("Mujer Maravilla");

    SuperPoder sp= new SuperFuerza("Super Fuerza");
    SuperPoder sp2= new Volar("Volar");

    ww.agregarPoder(sp2);
    ww.agregarPoder(sp);

    System.out.println("Superhéroe: "+ww.getNombre());
    System.out.println("\nPoderes: ");
    ww.getPoderes();

    SuperHeroe batman= new SuperHeroe();
    batman.setNombre("Batman");
    }
    }

    ResponderEliminar
  8. HOLA.. tengo una duda con algo del blog en este patrón. Por ejemplo la solución que se dio que de la Clase Duck hereda MallardDuck RedHeadDuck y RubberDuck. Cada uno de ellos, dependiendo lo que se necesite, implementa las interfaces Flyable y quackable. ¿Por qué se dice que es una de las ideas más tontas que la jefa ha recibido?, es decir por qué se Duplica el código de esa manera? Si hay que hacer una modificación a fly(), de qué manera se modificarían las más de 48 clases, si están con interfaz? Es decir que no entiendo el error de ese diseño -- > http://1.bp.blogspot.com/-W0BnJmLKZ4w/UQP6ox7vWhI/AAAAAAAAAqo/NL91YA0mD7w/s1600/DesignPatterns_Strategy4.png

    ResponderEliminar
  9. Hola Andrés Felipe!

    A lo que se refería la jefa es que como Flyable y Quackable son interfaces no implementan el código para fly() y quak() respectivamente sino que dejan la implementación a las clases que las usan, por lo que el código implementado existe repetidamente en MallardDuck, RedHeadDuck y RubberDuck y si existiera un cambio a este código tendrias que entrar a cada clase a hacer el cambio en la implementación, algo así:

    Imagina que el código para fly() que deben implementar las clases que la usan es:

    public void fly(){
    System.out.println("Volando");
    //Algo extra para cada tipo de pato
    }

    este código debe existir por cada clase que implemente a "Flyable", por lo que ya lo tienes repetido. Ahora, si decides cambiarlo por algo más complejo como saber si tiene la edad adecuada para volar, o si tiene las plumas necesarias para poder hacerlo, tendrías que modificar a algo como esto:

    public void fly(){
    if(this.edad > 1 && this.isEmplumado == true){ //puedes omitir el == true
    System.out.println("Volando");
    //Algo extra para cada tipo de pato
    }
    }

    con esto tendrías que ir a cada clase y modificar el método. Como vez este es un ejemplo sencillo y podrás pensar en hacer una refactorización directo desde tu IDE Eclipse o NetBeans que lo permiten pero, la cosa puede ser más compleja porque la edad y el emplumado puede depender para cada tipo de pato, por ejemplo para el RedHeadDuck puede ser a una edad de 2 años y para el MallardDuck a la edad 5 meses ...

    Espero te sirva la explicación =) Saludos!

    ResponderEliminar
  10. HOLA,,, mil gracias.. he estado perdido por mucho trabajo.. pero seguiré en tu blog luego compartiré experiencias y nuevos patrones... SALUDOS

    ResponderEliminar
  11. Felicidades por tu blog muy bien explicado y facil de entender, como observacion creo que tambien aplicas el patron null object en la clase FlyNoWay, espero no equivocarme.
    Pd. esperando el proximo post sobre patrones de diseño

    ResponderEliminar
  12. Buen post, pero deberias de haber añadido la fuente donde has conseguido el ejemplo: El libro se llama Head First Design Patterns.
    Muchas gracias por traducirlo al castellano.

    ResponderEliminar
  13. Al fin entendí este patrón! Muchas gracias!

    ResponderEliminar
  14. oye requiero ayuda tengo un codigo de una aplicacion del juego del gato solo que necesito ocupar uun patron de diseño como lo ago

    ResponderEliminar

Categories

Seguidores

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