Pregunta

He consultado esta explicación en Wikipedia , específicamente la muestra de C ++, y no reconozca la diferencia entre solo definir 3 clases, crear instancias y llamarlas, y ese ejemplo. Lo que vi fue simplemente colocar otras dos clases en el proceso y no puedo ver dónde podría haber un beneficio. Ahora estoy seguro de que me falta algo obvio (madera para los árboles). ¿Podría alguien explicarlo utilizando un ejemplo definitivo del mundo real?


Lo que puedo hacer de las respuestas hasta ahora, me parece que es solo una forma más compleja de hacer esto:

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[Editar-actualizar] La función a la que me refiero anteriormente se reemplaza con otra clase en la que MoveAlong sería un atributo que se establece según la necesidad en función del algoritmo implementado en esta nueva clase. (Similar a lo que se demuestra en la respuesta aceptada).


[Editar-actualización] Conclusión

El Patrón de Estrategia tiene sus usos, pero creo firmemente en KISS, y tendería a técnicas más sencillas y menos ofuscantes. Sobre todo porque quiero transmitir un código fácil de mantener (¡y porque es muy probable que yo sea el que tenga que hacer los cambios!).

¿Fue útil?

Solución

El punto es separar los algoritmos en clases que se pueden conectar en tiempo de ejecución. Por ejemplo, supongamos que tiene una aplicación que incluye un reloj. Hay muchas formas diferentes de dibujar un reloj, pero en su mayor parte la funcionalidad subyacente es la misma. Para que pueda crear una interfaz de visualización de reloj:

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

Luego tienes tu clase de reloj que está conectada a un temporizador y actualiza la pantalla del reloj una vez por segundo. Entonces tendrías algo como:

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

Luego, en tiempo de ejecución, crea una instancia de su reloj con la clase de visualización adecuada. es decir, podría tener ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian implementando la interfaz IClockDisplay.

Para que luego pueda agregar cualquier tipo de pantalla de reloj nueva creando una nueva clase sin tener que meterse con su clase de reloj, y sin tener que anular los métodos que pueden ser complicados para mantener y depurar.

Otros consejos

En Java, utiliza una secuencia de entrada de cifrado para descifrar así:

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

Pero el flujo de cifrado no tiene conocimiento de qué algoritmo de cifrado pretende utilizar o el tamaño del bloque, la estrategia de relleno, etc. Se agregarán nuevos algoritmos todo el tiempo, por lo que no es práctico codificarlos. En su lugar, pasamos un objeto de estrategia de cifrado para indicarle cómo realizar el descifrado ...

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

En general, utiliza el patrón de estrategia cada vez que tiene un objeto que sabe lo que necesita hacer pero no cómo para hacerlo. Otro buen ejemplo son los administradores de diseño en Swing, aunque en ese caso no funcionó tan bien, vea Totally GridBag para una ilustración divertida.

NB: Hay dos patrones en funcionamiento aquí, ya que el ajuste de flujos en flujos es un ejemplo de Decorador .

Hay una diferencia entre la estrategia y la decisión / elección. La mayoría de las veces, estaremos manejando decisiones / elecciones en nuestro código, y las realizaremos utilizando las construcciones if () / switch (). El patrón de estrategia es útil cuando hay una necesidad de desacoplar la lógica / algoritmo del uso.

Como ejemplo, piense en un mecanismo de sondeo, donde diferentes usuarios verificarían los recursos / actualizaciones. Ahora podemos querer que se notifique a algunos de los usuarios privilegiados con un tiempo de respuesta más rápido o con más detalles. Esencialmente la lógica que se está utilizando cambia en función de los roles de los usuarios. La estrategia tiene sentido desde un punto de vista de diseño / arquitectura, a niveles más bajos de granularidad, siempre se debe cuestionar.

El patrón de estrategia le permite explotar el polimorfismo sin extender su clase principal. En esencia, está colocando todas las partes variables en la interfaz de estrategia y las implementaciones y los delegados de la clase principal a ellas. Si su objeto principal utiliza solo una estrategia, es casi lo mismo que tener un método abstracto (virtual puro) y diferentes implementaciones en cada subclase.

El enfoque de estrategia ofrece algunos beneficios:

  • puede cambiar la estrategia en tiempo de ejecución: compárelo con cambiar el tipo de clase en tiempo de ejecución, que es mucho más difícil, específico del compilador e imposible para métodos no virtuales
  • una clase principal puede usar más de una estrategia que le permite recombinarlas de múltiples maneras. Considere una clase que recorre un árbol y evalúa una función basada en cada nodo y el resultado actual. Puede tener una estrategia de caminar (primero en profundidad o primero en amplitud) y una estrategia de cálculo (algún functor, es decir, "contar números positivos" o "suma"). Si no utiliza estrategias, deberá implementar una subclase para cada combinación de caminata / cálculo.
  • El código
  • es más fácil de mantener ya que modificar o comprender la estrategia no requiere que comprenda todo el objeto principal

El inconveniente es que, en muchos casos, el patrón de estrategia es una exageración: el operador de cambio / caso está ahí por una razón. Considere comenzar con simples declaraciones de flujo de control (cambio / caso o si), luego solo si es necesario pasar a la jerarquía de clases y si tiene más de una dimensión de variabilidad, extraiga las estrategias. Los punteros de función caen en algún lugar en el medio de este continuo.

Lectura recomendada:

Una forma de ver esto es cuando tiene una variedad de acciones que desea ejecutar y esas acciones se determinan en el tiempo de ejecución. Si crea una tabla hash o un diccionario de estrategias, podría recuperar las estrategias que corresponden a los valores o parámetros del comando. Una vez que se selecciona su subconjunto, simplemente puede iterar la lista de estrategias y ejecutar en sucesión.

Un ejemplo concreto sería calcular el total de un pedido. Sus parámetros o comandos serían precio base, impuesto local, impuesto municipal, impuesto estatal, envío terrestre y descuento de cupón. La flexibilidad entra en juego cuando maneja la variación de los pedidos: algunos estados no tendrán impuestos sobre las ventas, mientras que otros pedidos deberán aplicar un cupón. Puede asignar dinámicamente el orden de los cálculos. Siempre que haya tenido en cuenta todos sus cálculos, puede acomodar todas las combinaciones sin volver a compilar.

Este patrón de diseño permite encapsular algoritmos en clases.

La clase que usa la estrategia, la clase cliente, está desacoplada de la implementación del algoritmo. Puede cambiar la implementación de los algoritmos o agregar un nuevo algoritmo sin tener que modificar el cliente. Esto también se puede hacer dinámicamente: el cliente puede elegir el algoritmo que usará.

Por ejemplo, imagine una aplicación que necesita guardar una imagen en un archivo; La imagen se puede guardar en diferentes formatos (PNG, JPG ...). Los algoritmos de codificación se implementarán en diferentes clases que comparten la misma interfaz. La clase de cliente elegirá uno según la preferencia del usuario.

En el ejemplo de Wikipedia, esas instancias se pueden pasar a una función que no tiene que importar a qué clase pertenecen esas instancias. La función solo llama a execute en el objeto pasado y sabe que sucederá lo correcto.

Un ejemplo típico del Patrón de estrategia es cómo funcionan los archivos en Unix. Dado un descriptor de archivo, puede leerlo, escribirlo, sondearlo, buscarlo, enviarle ioctl s, etc., sin tener que saber si está tratando con un archivo , directorio, tubería, zócalo, dispositivo, etc. (Por supuesto, algunas operaciones, como la búsqueda, no funcionan en tuberías y enchufes. Pero las lecturas y escrituras funcionarán bien en estos casos).

Eso significa que puede escribir código genérico para manejar todos estos diferentes tipos de "archivos", sin tener que escribir código separado para tratar con archivos versus directorios, etc. El núcleo de Unix se encarga de delegar las llamadas al código correcto .

Ahora, este es el Patrón de estrategia utilizado en el código del kernel, pero no especificó que tenía que ser un código de usuario, solo un ejemplo del mundo real. :-)

El patrón de estrategia funciona en una idea simple, es decir, "Favorecer la composición sobre la herencia". para que la estrategia / algoritmo se pueda cambiar en tiempo de ejecución. Para ilustrar, tomemos un ejemplo en el que necesitamos cifrar diferentes mensajes según su tipo, por ejemplo. MailMessage, ChatMessage, etc.

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

Ahora, en tiempo de ejecución, puede instanciar diferentes Mensajes heredados de CMessage (como CMailMessage: public CMessage) con diferentes encriptadores (como CDESEncryptor: public CEncryptor)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top