2010-01-06 10 views
13

El siguiente ejemplo de código es una implementación del patrón de estrategia copied from Wikipedia. Mi pregunta completa que sigue ...¿Este patrón de estrategia Java tiene una clase de contexto redundante?

main El método del Wiki:

//StrategyExample test application 

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); 

    } 

} 

Las piezas del patrón:

// The classes that implement a concrete strategy should implement this 

// The context class uses this to call the concrete strategy 
interface Strategy { 

    int execute(int a, int b); 

} 

// Implements the algorithm using the strategy interface 
class ConcreteStrategyAdd implements Strategy { 

    public int execute(int a, int b) { 
     System.out.println("Called ConcreteStrategyA's execute()"); 
     return a + b; // Do an addition with a and b 
    } 

} 

class ConcreteStrategySubtract implements Strategy { 

    public int execute(int a, int b) { 
     System.out.println("Called ConcreteStrategyB's execute()"); 
     return a - b; // Do a subtraction with a and b 
    } 

} 

class ConcreteStrategyMultiply implements Strategy { 

    public int execute(int a, int b) { 
     System.out.println("Called ConcreteStrategyC's execute()"); 
     return a * b; // Do a multiplication with a and b 
    } 

} 

// Configured with a ConcreteStrategy object and maintains a reference to a Strategy object 
class Context { 

    private Strategy strategy; 

    // Constructor 
    public Context(Strategy strategy) { 
     this.strategy = strategy; 
    } 

    public int executeStrategy(int a, int b) { 
     return strategy.execute(a, b); 
    } 

} 

teniendo especialmente en cuenta el ejemplo anterior, es la clase Context redundante?

Por ejemplo, puedo obtener la siguiente implementación alternativa de main utilizando las clases y la interfaz existentes excepto Context y funcionará exactamente igual. Todavía está débilmente acoplado.

((Editar:? En este escenario simple, cuando salgo de la clase Context, voy a estar haciendo un futuro error))

public static void main(String[] args) { 

    IStrategy strategy; 

    // Three strategies 
    strategy = new ConcreteStrategyAdd(); 
    int resultA = strategy.executeStrategy(3,4); 

    strategy = new ConcreteStrategySubtract(); 
    int resultB = strategy.executeStrategy(3,4); 

    strategy = new ConcreteStrategyMultiply(); 
    int resultC = strategy.executeStrategy(3,4); 

} 

cotización Actualizar

Listado en forma de punto lo que se descubrió a través de respuestas y comentarios:

  • El contexto permite la variación en cómo se usa la estrategia compuesta (p. tiempo de su llamada). Diferentes contextos pueden hacer un trabajo interno diferente antes y después de llamar a la estrategia dada.
  • El contexto es un "cuadro negro" de alto nivel. La lógica de Contexto puede cambiar, también la Estrategia compuesta puede cambiar (o usarse una diferente) sin romper el cliente porque el cliente solo entiende cómo llamar al contexto.
  • Aunque creé una implementación alternativa del código de muestra de Wikipedia al omitir el contexto, y aunque funcionaba igual que el original, la situación se simplificó (en ambos casos) y mis cambios realmente significaron: 1. es ya no es un patrón de Estrategia, 2. Extraño los beneficios del espíritu del patrón de Estrategia que se mencionan aquí.
  • Mi implementación alternativa usó el método principal como un contexto, así que también podría mantener el contexto si lo simula de manera efectiva. Al crear un patrón de estrategia impuro, se creó confusión. No necesité reinventar la rueda o intentar ser más inteligente (en este caso).

Si algún otro punto fuera útil o si necesita corrección, deje un comentario y modificaré la lista en consecuencia.

Respuesta

15

Como su nombre indica, el Context es lo que encapsula el punto en el que se realiza la estrategia. Sin eso, solo tiene un Strategy desnudo, y la clase llamante ahora asume una responsabilidad adicional: saber cuándo llamar al Strategy. Su ejemplo es tal vez un poco demasiado simple, y en este caso particular, diría que el Context no le está consiguiendo demasiado.

Un ejemplo que ilustra tal vez mejor la utilidad de un Context es más parecido a lo siguiente:

public class LoadingDock { // Context. 
    private LoadStrategy ls; // Strategy. 

    public void setLoadStrategy(LoadStrategy ls) { ... } 

    // Clients of LoadingDock use this method to do the relevant work, rather 
    // than taking the responsibility of invoking the Strategy themselves. 
    public void shipItems(List<ShippingItem> l) { 
    // verify each item is properly packaged  \ 
    // ...          | This code is complex and shouldn't be 
    // verify all addresses are correct   | subsumed into consumers of LoadingDock. 
    // ...          | Using a Context here is a win because 
    // load containers onto available vehicle  | now clients don't need to know how a 
    Vehicle v = VehiclePool.fetch();  // | LoadingDock works or when to use a 
    ls.load(v, l);       // / LoadStrategy. 
    } 
} 

Aviso cómo el Strategy no serán llamados directamente desde un cliente externo. Solo shipItems usa la estrategia, y los detalles de los pasos que sigue son una caja negra. Esto permite que Context ajuste cómo usa la estrategia sin afectar a los clientes. Por ejemplo, los pasos podrían reordenarse por completo o ajustarse (o eliminarse por completo) para cumplir con los objetivos de rendimiento u otros objetivos, pero para el cliente, la interfaz externa de shipItems() tiene el mismo aspecto.

Observe, también, que nuestro ejemplo Context, LoadingDock, podría cambiar su LoadStrategy en cualquier momento en función de su estado interno. Por ejemplo, si el muelle se está llenando demasiado, tal vez cambie a un mecanismo de programación más agresivo que saque las cajas del muelle y hacia los camiones más rápido, sacrificando algo de eficiencia al hacerlo (tal vez los camiones no se cargan tan eficientemente como podrían haber sido).

+1

Esta es una explicación completamente detallada que describe claramente la importancia del contexto, incluida la muestra de código de apoyo. Estupendo. –

+0

Supongo, además, que una clase de contexto se puede subclasificar para agregar comportamiento al trabajo de contexto base (es decir, volver a llamar al procesamiento de contexto base primero antes de realizar su trabajo adicional). Lo mismo podría hacerse con cualquier estrategia concreta. Esto permitiría que las partes varíen el comportamiento de manera independiente, pero que sean compatibles con los tipos básicos. Así que estoy empezando a darme cuenta de que hay mucha flexibilidad tanto horizontalmente mediante implementaciones alternativas como verticalmente a través de la herencia, y también combinaciones de ellas. Interesante. Las cosas también pueden complicarse rápidamente si no se las piensa. –

+0

Hmm. No estoy seguro de que me guste la idea de un 'Contexto' polimórfico. OMI, sería mejor componer las diferentes piezas en un solo 'Contexto' coherente, y luego tener las estrategias apropiadas para cada uno según corresponda. –

3

Podría ser para este ejemplo inventado, pero entonces no lo llamaría el ne plus ultra de Strategy.

La clase Context está demostrando cómo puede darle a una clase un comportamiento diferente simplemente al pasar una nueva implementación concreta de la interfaz. Como la clase solo conoce la interfaz, nada tiene que cambiar. Ese es el punto. No tome el resto del ejemplo demasiado literalmente.

La forma en que lo codificó funcionará, pero el punto es que ha descargado esto en un método principal. Esa no será la forma en que normalmente usarás la Estrategia.Lo harás dentro de una clase, y Context es un simple ejemplo de eso.

+0

Ya veo lo que quieres decir. Estaba sustituyendo efectivamente un método principal para la clase de Contexto, lo que plantea la pregunta de por qué no solo usar una clase Contexto como define el patrón de Estrategia, para abstraer el uso de la estrategia como describiste. Esto es útil para mí porque puedo pensarlo desde la perspectiva de lo que quería poner en el método principal para "simplificar" las cosas (por ejemplo, hacerlas un poco incorrectas) y darme cuenta de que eso es lo que puedo poner en un contexto. Gracias. –

+0

'Agregué una sección Actualización de resumen debajo de la pregunta original para consolidar los hallazgos.' –

4

Este es el mejor ejemplo de cómo el verdadero "Context" clase puede mirar en este escenario:

class Accumulator { 
    private Strategy strategy; 

    public Accumulator(Strategy strategy) { 
     this.strategy = strategy; 
    } 

    public int accumulate(List<Integer> values) { 
     int result = values.get(0); 
     for (int i = 1; i < values.size(); i++) { 
      result = strategy.execute(result, values.get(i)); 
     } 
     return result; 
    } 
} 

EDIT: del error tipográfico en el constructor fijo

+0

¿Estoy viendo bien, el nombre de clase es "Acumulador" y el nombre del constructor es "Contexto"? Vengo del mundo C# así que tal vez estoy confundiendo algo aquí y faltando tu punto. –

+1

Parece que el constructor tiene un error tipográfico. –

0

Context no será redundante en Strategy patrón y es útil en escenarios siguientes:

  1. El código para llamar a un determinado Strategy se esparce en múltiples clases sin invocar Context. En el futuro, si tiene que volver a factorizar o cambiar la interfaz Strategy, será una tarea agitada.
  2. Supongamos que es necesario rellenar algunos datos antes de llamar a una estrategia en particular. El contexto se ajusta mejor aquí proporcionando información adicional y llamando al método estratégico de una estrategia en particular.

    p. Ej. Context obtendrá una estrategia y userId como parámetro. Antes de ejecutar Strategy, Context necesita proporcionar mucha información adicional relacionada con el perfil del usuario. Context obtendrá la información requerida y ejecutará el método estratégico de Estrategia. En ausencia de Contexto, debe duplicar el código en 100 lugares diferentes si llama al método estratégico en 100 lugares diferentes.

  3. Context pueden tomar decisiones independientes sobre qué estrategia invocar. Puede cambiar simplemente el tipo de estrategia dependiendo de la configuración del tiempo de ejecución. Estrategia core USP está cambiando entre la familia del algoritmo relacionado. El contexto es el mejor lugar para lograrlo.

  4. Si tiene que actuar en múltiples estrategias, Context es el mejor lugar.La respuesta propuesta axtavt de usar Accumulator es un ejemplo.

Consulte esta publicación más detalles.

Real World Example of the Strategy Pattern

Cuestiones relacionadas