JavaRush /Blog Java /Random-ES /Patrón de diseño “Estrategia”

Patrón de diseño “Estrategia”

Publicado en el grupo Random-ES
¡Hola! En conferencias anteriores, ya nos hemos encontrado con el concepto de "patrón de diseño". En caso de que lo hayas olvidado, permítenos recordarte: este término denota una determinada solución estándar a un problema común en programación. Patrón de diseño “Estrategia” - 1En JavaRush solemos decir que la respuesta a casi cualquier pregunta se puede buscar en Google. Por lo tanto, probablemente alguien ya haya resuelto con éxito un problema similar al suyo. Por lo tanto, los patrones son soluciones probadas en el tiempo y en la práctica para los problemas o métodos más comunes para resolver situaciones problemáticas. Estas son las mismas "bicicletas" que en ningún caso necesitas inventar tú mismo, pero necesitas saber cómo y cuándo aplicarlas :) Otra tarea de los patrones es llevar la arquitectura a un estándar único. ¡Leer el código de otra persona no es una tarea fácil! Cada uno lo escribe de forma diferente, porque un mismo problema se puede solucionar de muchas formas. Pero el uso de patrones permite a diferentes programadores comprender la lógica del programa sin profundizar en cada línea de código (¡incluso si lo ven por primera vez!). Hoy veremos uno de los patrones más comunes llamado "Estrategia". Patrón de diseño “Estrategia” - 2Imaginemos que estamos escribiendo un programa que trabaja activamente con el objeto Car. En este caso, ni siquiera es particularmente importante qué hace exactamente nuestro programa. Para hacer esto, creamos un sistema de herencia con una clase principal Autoy tres clases secundarias Sedan: Trucky F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
Las tres clases secundarias heredan dos métodos estándar del padre, gas()y stop() nuestro programa es muy simple: los autos solo pueden avanzar y frenar. Continuando con nuestro trabajo, decidimos agregar un nuevo método a los automóviles: fill()(repostar combustible). Agreguémoslo a la clase principal Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
¿Parecería que podrían surgir problemas en una situación tan simple? Bueno, de hecho, ya han surgido... Patrón de diseño “Estrategia” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
En nuestro programa apareció un automóvil que no encaja en el concepto general: un cochecito para niños. Puede que funcione con pedales o con control por radio, pero una cosa es segura: no hay ningún lugar donde ponerle gasolina. Nuestro esquema de herencia ha resultado en que regalemos métodos comunes incluso a clases que no los necesitan. ¿Qué debemos hacer en tal situación? Bueno, por ejemplo, puedes anular el método fill()en la clase ChildrenBuggiespara que cuando intentes repostar el buggy, no pase nada:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
Pero esta solución difícilmente puede considerarse exitosa, al menos debido a la duplicación de código. Por ejemplo, la mayoría de las clases utilizarán un método de la clase principal, pero otras clases se verán obligadas a anularlo. Si tenemos 15 clases y en 5-6 nos vemos obligados a anular el comportamiento, la duplicación de código será bastante extensa. ¿Quizás las interfaces puedan ayudarnos? Por ejemplo, este:
public interface Fillable {

   public void fill();
}
Crearemos una interfaz Fillablecon un método fill(). En consecuencia, aquellos coches que necesiten repostar combustible implementarán esta interfaz, pero otros coches (por ejemplo, nuestro buggy) no. Pero esta opción tampoco nos conviene. Nuestra jerarquía de clases puede crecer hasta alcanzar un número muy grande en el futuro (imagínese cuántos tipos diferentes de automóviles hay en el mundo). Abandonamos la opción de herencia anterior porque no queríamos anular el archivo fill(). ¡Aquí tendremos que implementarlo en cada clase! ¿Qué pasa si tenemos 50 de ellos? Y si se realizan cambios frecuentes en nuestro programa (¡y en los programas reales esto casi siempre será así!), tendremos que correr con la lengua fuera entre las 50 clases y cambiar el comportamiento de cada una de ellas manualmente. Entonces, ¿qué deberíamos hacer al final? Para resolver nuestro problema, elijamos un camino diferente. Es decir, separemos el comportamiento de nuestra clase de la clase misma. ¿Qué significa? Como sabes, cualquier objeto tiene un estado (un conjunto de datos) y un comportamiento (un conjunto de métodos). El comportamiento de nuestra clase de máquina consta de tres métodos: gas(), stop()y fill(). Los dos primeros métodos están bien. Pero moveremos el tercer método fuera de la clase Auto. Esta será la separación del comportamiento de la clase (más precisamente, separamos solo una parte del comportamiento; los dos primeros métodos permanecen vigentes). ¿ Hacia dónde deberíamos mover nuestro método fill()? Inmediatamente no se me ocurre nada :/ Parecía estar completamente en su lugar. Lo moveremos a una interfaz separada - FillStrategy!
public interface FillStrategy {

   public void fill();
}
¿Por qué necesitamos esta interfaz? Es sencillo. Ahora podemos crear varias clases que implementarán esta interfaz:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином o электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedimientos пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
Hemos creado tres estrategias de comportamiento: para coches convencionales, para híbridos y para coches de Fórmula 1. Cada estrategia implementa un algoritmo de repostaje independiente. En nuestro caso, esto solo se envía a la consola, pero puede haber alguna lógica compleja dentro del método. ¿Qué debemos hacer con esto a continuación?
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
Usamos nuestra interfaz FillStrategycomo un campo en la clase principal Auto. Tenga en cuenta: no especificamos una implementación específica, sino que utilizamos la interfaz. Y necesitaremos implementaciones específicas de la interfaz FillStrategyen las clases de coches infantiles:
public class F1Car extends Auto {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridAuto extends Auto {

   public HybridAuto() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
Veamos qué tenemos:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Salida de consola:

Просто заправляем бензин!
Заправляем бензином o электричеством на выбор!
Заправляем бензин только после всех остальных procedimientos пит-стопа!
¡Genial, el proceso de reabastecimiento de combustible funciona como debería! Por cierto, ¡nada nos impide utilizar la estrategia como parámetro en el constructor! Por ejemplo, así:
public class Auto {

   private FillStrategy fillStrategy;

   public Auto(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

   public Sedan() {
       super(new StandartFillStrategy());
   }
}



public class HybridAuto extends Auto {

   public HybridAuto() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
¡Ejecutemos nuestro método main()(permanece sin cambios) y obtengamos el mismo resultado! Salida de consola:

Просто заправляем бензин!
Заправляем бензином o электричеством на выбор!
Заправляем бензин только после всех остальных procedimientos пит-стопа!
El patrón Estrategia define una familia de algoritmos, encapsula cada uno de ellos y garantiza que sean intercambiables. Le permite modificar algoritmos independientemente de su uso en el lado del cliente (esta definición está tomada del libro "Exploración de patrones de diseño" y me parece extremadamente acertada). Patrón de diseño “Estrategia” - 4Hemos aislado la familia de algoritmos que nos interesan (tipos de coches de repostaje) en interfaces separadas con varias implementaciones. Los hemos separado de la esencia misma del coche. Por lo tanto, ahora, si necesitamos realizar algún cambio en tal o cual proceso de repostaje, esto no afectará de ninguna manera a nuestras clases de coches. En cuanto a la intercambiabilidad, para lograrlo solo necesitamos agregar un método setter a nuestra clase Auto:
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Ahora podemos cambiar de estrategia sobre la marcha:
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
Si de repente los cochecitos de los niños empiezan a llenarse de gasolina, nuestro programa estará preparado para tal escenario :) ¡Eso es todo, de hecho! Has aprendido otro patrón de diseño, que sin duda necesitarás y te ayudará más de una vez cuando trabajes en proyectos reales :) ¡Nos vemos de nuevo!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION