strategy plantilla patrones patron metodo explicacion estrategia ejemplos diseño design-patterns strategy-pattern

design patterns - plantilla - ¿Cuándo y por qué debería usarse el Patrón de Estrategia?



patron visitor explicacion (10)

¿Cuándo se usaría el Patrón de Estrategia ?

Veo fragmentos de código de cliente como este:

class StrategyExample { public static void main(String[] args) { Context context; // Three contexts following different strategies context = new Context(new ConcreteStrategyAdd()); int resultA = context.executeStrategy(3,4); context = new Context(new ConcreteStrategySubtract()); int resultB = context.executeStrategy(3,4); context = new Context(new ConcreteStrategyMultiply()); int resultC = context.executeStrategy(3,4); } }

y parece que podrías refactorizarlo a esto:

class StrategyExample { public static void main(String[] args) { // Three contexts following different strategies int resultA =new ConcreteStrategyAdd().execute(3,4); int resultB =new ConcreteStrategySubtract().execute(3,4); int resultC =new ConcreteStrategyMultiply().execute(3,4); } }

La primera sección del código fue tomada directamente de la página de wikipedia. Una gran diferencia es que el contexto desaparece, pero de todos modos no estaba haciendo nada en el ejemplo. Tal vez alguien tenga un mejor ejemplo donde la estrategia tenga sentido. Generalmente me gustan los patrones de diseño, pero este parece agregar complejidad sin agregar utilidad.


Áreas que vienen a la mente:

  • Un asignador de recursos. En la administración de recursos manual, esto podría ser minimizar el tiempo que tarda el recurso en asignar o minimizar la fragmentación. Cada estrategia aquí tiene un método de "Asignación" que tiene la misma interfaz, y el usuario toma una decisión sobre qué estrategia usar en función de lo que está tratando de optimizar.
  • Un método para conectar y enviar datos de red. Tal vez, en algunos casos, prefiera conectarse y enviar datagramas UDP, tal vez en otras situaciones en las que el rendimiento fue un factor menos importante que enviaría utilizando TCP / IP.
  • Formato de datos / estrategia de serialización. Permita que el código decida si un objeto debe ser serializado con Json o con Xml. Tal vez uno para máquinas, y otro para situaciones legibles por humanos. Ambas estrategias tienen un método de "Serialización" que toma un objeto. Cada uno se serializa de manera diferente.

El tema es que la decisión de hacer algo de una manera u otra depende de los factores de la situación, y usted o su código elegirán la estrategia correcta según la situación.

Ahora por qué esto sería más útil que algo como:

void DoIt() { if (... situation1...) { DoA() } else { DoB(); } }

La razón es que a veces solo quieres tomar la decisión una vez y olvidarte de ella. El otro tema importante para el patrón de estrategia es que desacopla la decisión sobre qué estrategia usar del código que necesita para ejecutar la estrategia.

DoItStrategy MakeDoItStrategy() { if (... situation1...) { return new DoItStrategyA(); } else { return new DoItStrategyB(); } }

En el último ejemplo, solo puede almacenar la estrategia, pasándola como otro objeto que implementa la interfaz de la estrategia. Para aquellos que ejecutan la estrategia, simplemente tienen una forma de realizar una acción. No saben cuáles son los trabajos internos debajo del capó, solo que la interfaz quedará satisfecha. Los usuarios de la estrategia no deberían saber por qué tomamos una decisión. Solo necesitan hacer una acción. Tomamos una decisión una vez y pasamos la estrategia a las clases que la usan.

Por ejemplo, considere el caso en el que tomamos una decisión de todo el programa, basada en una configuración de red determinada, para conectar y enviar datos a hosts remotos con UDP. En lugar de que cada usuario de la interfaz de red necesite conocer la lógica para tomar la decisión (la función "DoIt" más arriba), podemos crear la estrategia UDP por adelantado y pasarla a todos los que necesiten enviar datos de red. Esta estrategia implementa una interfaz simple con el mismo resultado final: los datos se transfieren de A a B.


El Context sabe cómo hacer algo complicado, dada una operación que le proporcionas. Las operaciones son cosas simples (sumar dos números, multiplicar dos números, etc.). Un Context , en un ejemplo no trivial, puede requerir cualquier número de comportamientos diferentes (no solo uno), que se consideran mejor como "estrategias" (ya que tratar de crear subclases de Context crearía una explosión combinatoria de subclases).


El fragmento de código que está citando es un poco engañoso ya que está (un poco) fuera de contexto. Lo que está escribiendo a continuación en su ejemplo es también el patrón de estrategia: acaba de reescribir el ejemplo anterior de manera más concisa.

El punto principal en el ejemplo es que los detalles de la operación matemática se extraen de la persona que llama. De esta manera, la persona que llama puede trabajar con cualquier operador binario creando una nueva estrategia de concreto, por ejemplo,

int mod = new ConcreteStrategy(){ public int execute(int a, int b){ return a %b; } }.execute(3,4);


El patrón de estrategia es útil en situaciones donde usted (o los usuarios de su código) pueden querer cambiar los cálculos en sus algoritmos. Un ejemplo simple donde he usado el patrón de estrategia es en el modelado de heurísticas en una búsqueda A *. A * utiliza heurísticas, que son cálculos simples para estimar el costo restante si se elige un determinado nodo (Ni) en la ruta hacia el nodo objetivo (Ng). Mi interfaz se veía algo como esto:

class Heuristic { public: virtual int estimateRemainingCost(const node &Ni, const node &Ng) const = 0; };

Y se usan así:

... // for each node (Ni) that is adjacent to the current node Nc int node_priority = cost(Ni)/* the cost of choosing this node on the path */ + heuristic->estimateRemainingCost(Ni, Ng); unsearched_nodes_.queue(node_priority, Ni); ...

Las heurísticas son estrategias o cálculos que pueden ser reemplazados. En otras palabras, es un ejercicio trivial para cambiar la heurística del algoritmo de búsqueda.


El problema con los ejemplos de juguetes como este es que a menudo es fácil perder el punto. En este caso, el código podría implementarse como se muestra. En un patrón de estrategia, el valor principal es poder cambiar diferentes implementaciones para diferentes situaciones.

El ejemplo que tienes solo ilustra los objetos en el patrón y las interacciones entre ellos. En cambio, imagina que tienes un componente que representa gráficos para un sitio web dependiendo de si se trata de un navegador web real o un teléfono inteligente en el otro extremo, tendrías algún código que detectaría el tipo de navegador que crearía y establecería la estrategia en otro. componente que podría usar el objeto de estrategia en un código complejo que no tendría que ser duplicado y haría el trabajo en ambas situaciones dejando los detalles del dibujo real de la gráfica en el objeto de estrategia apropiado:

interface GraphStrategy { Image renderGraph(Data graphData); } class BigGraphStratedy implements GraphStrategy { ... } class SmallGraphStrategy implements GraphStrategy { ... }

Luego en algún otro código:

GraphStrategy graphStrategy; if (phoneBrowser == true) { graphStrategy = new SmallGraphStrategy(); } else { graphStrategy = new BigGraphStrategy(); }

El resto del código de la aplicación puede usar graphStrategy.renderGraph() sin tener que saber si se está realizando un renderizado de imágenes completo o pequeño.


La principal diferencia es que en el segundo ejemplo, la estrategia es el algoritmo (por lo tanto, no hay patrón). En el primer ejemplo, está abstrayendo / aislando una parte del algoritmo.

Por ejemplo, la implementación de Context.executeStrategy() podría ser:

public int executeStrategy(int baseValue, int exponentFactor) { return (int) Math.Pow(baseValue, this.Strategy.Calculate(2, exponentFactor)); }


Los marcos de Cocoa utilizados en Mac y iPhone usan mucho el patrón de estrategia, excepto que lo llamamos el Patrón de Delegado. Así es como lo usamos:

Tenemos un objeto concreto, digamos un NSTableView . La vista de tabla necesita saber cuántas filas tiene, qué va en cada fila, cada columna, etc. Entonces, en lugar de subclasificar la vista de tabla para proporcionar esa información, proporcionamos un objeto "delegar". Ese objeto delegado implementa una interfaz determinada ("protocolo" en Objective-C). Luego, la vista de tabla puede simplemente preguntar a su objeto delegado qué debe hacer en ciertas situaciones ("¿Cuántas filas tengo?" "¿Qué pasa en esta celda?" "¿El usuario tiene permiso para seleccionar esta fila?"). Podemos intercambiar el objeto delegado en tiempo de ejecución simplemente asignando un nuevo objeto que se ajuste al protocolo NSTableViewDelegate .

Así que sí, el patrón de estrategia es uno de mis favoritos y uno que uso todos los días.


Normalmente utilizo el patrón de estrategia cuando tengo muchas cosas que hacer, según la situación. En esencia, es una forma de transformar una serie larga de sentencias if / else en unas pocas líneas.

Una forma de hacer esto (en Java):

Map<String, Strategy> strategyMap = new HashMap<String, Strategy>(); strategyMap.put("bark", new BarkingStrategy()); strategyMap.put("meow", new MeowingStrategy()); strategyMap.put("moo", new MooingStrategy()); strategyMap.put("giraffeSound", new UnknownStrategy());

Primero construyes alguna forma de repertorio de estrategias.

Luego...

String command = //...some form of input strategyMap.get(command).execute();

De esta manera, puede manejar "genéricamente" muchas situaciones diferentes.

es decir:

moo

ejecutaría el MooingStrategy()


Sí, el ejemplo es escaso, pero el concepto de delegados con diferentes estrategias de implementación es un patrón de diseño básico, muy antiguo, que he usado en muchas y muchas aplicaciones.

Sin embargo, creo que su problema aquí es común, casi todos los ejemplos de patrones de diseño que he visto están increíblemente diseñados y siempre me llevan a hacer preguntas como las suyas: siempre parecen inútiles cuando se presentan de esta manera.


Tiene más sentido cuando el objeto de contexto tiene más responsabilidades y la abstracción de la estrategia separa estas responsabilidades de algún aspecto de la operación. Un ejemplo, (en C #) es la interfaz de IComparer:

interface IComparer<T> { int Compare(T a, T b); }

Que se puede pasar a un algoritmo de clasificación.