template strategy pattern patron example design-patterns strategy-pattern

design-patterns - patron - strategy pattern php



¿Dónde está el beneficio al usar el patrón de estrategia? (8)

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

El enfoque de estrategia ofrece algunos beneficios:

  • puede cambiar la estrategia en tiempo de ejecución: compare esto con cambiar el tipo de clase en el tiempo de ejecución, que es mucho más difícil, específico del compilador e imposible para los 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 (profundidad-primero o ancho-primero) y estrategia de cálculo (algunos funtores, es decir, "contar números positivos" o "suma"). Si no usa estrategias, deberá implementar una subclase para cada combinación de caminar / cálculo.
  • el código es más fácil de mantener ya que la estrategia de modificación o comprensión no requiere que entiendas todo el objeto principal

El inconveniente es que, en muchos casos, el patrón de estrategia es una exageración: el operador del conmutador / caja está allí por una razón. Considere comenzar con instrucciones simples 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 estrategias de ella. Los indicadores de función caen en algún lugar en el medio de este continuo.

Lectura recomendada:

He visto esta explicación en Wikipedia , específicamente la muestra de C ++, y no reconozco la diferencia entre simplemente 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 habría 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 con 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 arriba se reemplaza con otra clase en la que MoveAlong sería un atributo que se configura 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-actualizar] Conclusión

El Patrón de estrategia tiene sus usos, pero creo firmemente en KISS y tendería a técnicas más directas y menos ofuscatorias. Sobre todo porque quiero transmitir un código de fácil mantenimiento (y porque probablemente sea yo quien tenga que hacer los cambios).


El punto es separar los algoritmos en clases que pueden ser conectadas en el 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 tiene su clase Clock 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.

Entonces, puede agregar cualquier tipo de visualización de reloj nueva creando una nueva clase sin tener que meterse con su clase Clock, y sin tener que anular los métodos que pueden ser complicados de mantener y depurar.


En Java, utiliza una secuencia de entrada de cifrado para descifrar de la siguiente manera:

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

Pero el flujo de cifrado no tiene conocimiento de qué algoritmo de cifrado pretendes usar o el tamaño de bloque, 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 Cipher para decirle 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 tiene que hacer pero no cómo hacerlo. Otro buen ejemplo son los administradores de diseño en Swing, aunque en ese caso tampoco funcionó, vea Totally GridBag para una divertida ilustración.

NB: Aquí hay dos patrones en funcionamiento, ya que el envoltorio de las corrientes en las corrientes es un ejemplo de Decorator .


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 simplemente execute sobre el objeto pasado, y saber 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 leer, escribir, sondear, buscar, enviar ioctl s, etc., sin tener que saber si se trata de un archivo, directorio, conducto, socket, dispositivo, etc. (Por supuesto, algunas operaciones, como buscar, 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 un código separado para tratar con archivos en lugar de directorios, etc. El kernel de Unix se encarga de delegar las llamadas al código correcto.

Ahora, este es el Patrón de estrategia como se usa en el código del núcleo, pero no especificaste que tenía que ser un código de usuario, solo un ejemplo del mundo real. :-)


Hay una diferencia entre estrategia y decisión / elección. La mayoría de las veces, debemos manejar decisiones / elecciones en nuestro código y realizarlas usando 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 votación, donde diferentes usuarios verifiquen recursos / actualizaciones. Ahora podemos desear que algunos de los usuarios privados sean notificados con un tiempo de respuesta más rápido o con más detalles. Essentailly la lógica que se usa cambia según los roles del usuario. La estrategia tiene sentido desde un punto de vista de diseño / arquitectura, en niveles más bajos de granularidad siempre debe ser cuestionada.


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

Un ejemplo concreto sería calcular el total de una orden. Sus parámetros o comandos serían el precio base, impuestos locales, impuestos municipales, impuestos estatales, envío terrestre y descuento de cupones. 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 deberán aplicar un cupón. Puede asignar dinámicamente el orden de los cálculos. Siempre y cuando haya contabilizado 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 de cliente, está desacoplada de la implementación del algoritmo. Puede cambiar la implementación de algoritmos o agregar un nuevo algoritmo sin tener que modificar el cliente. Esto también se puede hacer de forma dinámica: 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 compartiendo la misma interfaz. La clase del cliente elegirá una según las preferencias del usuario.


El patrón de estrategia funciona con 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 encriptar 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: CMessage público) con diferentes encriptadores (como CDESEncryptor: public CEncryptor)

CMessage *ptr = new CMailMessage(); ptr->setEncryptor(new CDESEncrypto()); ptr->performEncryption();