Java Strategy Pattern - can I delegate the creation of strategies in the Context class?

I am currently learning design patterns on my own. When I examined the strategy pattern, I found something that looks strange to me. I searched for discussions on this schema, but no one answered my question ... how can I implement the strategy pattern to keep it clean, to support encapsulation and make it easier to add a new strategy. To explain my problem here - the "canonical" strategy pattern:

public interface Strategy {
    public void run();
}

public class stratConcrt1 implements Strategy {/*run() implementation*/}
public class stratConcrt2 implements Strategy {/*run() implementation*/}

public class Context {
    private Strategy strategy;

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

    public void runStrategy() {
        this.strategy.run()
    }
}


public class Client {
    public void main(Strings[] args) {
        Context cx;

        cx = new Context(new stratConcrt1())
        cx.runStrategy();

        cx = new Context(new stratConcrt2())
        cx.runStrategy();
    }
}

      

I understand what's going on, but it seems strange to me that the client knows something about the different strategies that might be applied. It would be easier for me to allow the Context to create different strategies rather than the Client, since the Context deals with strategies which it should (at least in my opinion) be the only one who can create the strategy.

I have implemented a small example using JavaFx with some differences from the code above:

I have a class field that creates a list of coordinates, this class has a method to sort the list. The coordinate list sorting method is a method for which there are several strategies.

public class Field {

    // a field contains rectangles described in a list through their coordinates
    private ObservableList<Coordinate> mpv_coordinateList = FXCollections
                    .observableArrayList();

    private Context mpv_sortContext;

    // Constructor
    public Field() {
        //the rectangles are randomly created
        Random rd = new Random();
        for (int i = 0; i < 100; i++) {
            mpv_coordinateList.add(new Coordinate(rd.nextInt(490), rd.nextInt(490)));
        }

        //a context to dynamically modify the sort algorithm
        mpv_sortContext = new Context();
    }

    //returns the list with all rectangle
    public ObservableList<Coordinate> getField() {
        return this.mpv_coordinateList;
    }

    //sort elements (depending on different algorithms)
    public ObservableList<Coordinate> sortElements(String p_sortToApply) {
        return mpv_sortContext.launchSort(p_sortToApply,
                        this.mpv_coordinateList);
    }
}

      

As the model says, I created an interface and gave specific strategies inherited from that interface:

public interface SortStrategy {
    ObservableList<Coordinate> sort(ObservableList<Coordinate> p_listToSort);
}

public class EvenSort implements SortStrategy {
    @Override
    public ObservableList<Coordinate> sort(
                ObservableList<Coordinate> p_listToSort) {

        ObservableList<Coordinate> oddCoordList = FXCollections
                        .observableArrayList();

        for (Coordinate coord : p_listToSort) {
            if (coord.x % 2 == 0) {
                oddCoordList.add(coord);
            }
        }
        return oddCoordList;
    }
}

public class OddSort implements SortStrategy {
    @Override
    public ObservableList<Coordinate> sort(
                ObservableList<Coordinate> p_listToSort) {

        ObservableList<Coordinate> oddCoordList = FXCollections
                        .observableArrayList();

        for (Coordinate coord : p_listToSort) {
            if (coord.x % 2 == 1) {
                oddCoordList.add(coord);
            }
        }
        return oddCoordList;
    }
}

      

Concrete classes simply return a list containing all coordinates with an even or odd x coordinate.

and then I created a class context:

public class Context {
    //private SortStrategy mpv_sortStrategy; //never used

    private EvenSort mpv_evenSort = new EvenSort();
    private OddSort mpv_oddSort = new OddSort();
    private StandardSort mpv_standardSort = new StandardSort();

    private HashMap<String, SortStrategy> mpv_HashMapStrategies;


    public Context() {

        //creation of a dictionary with all possible strategies
        mpv_HashMapStrategies = new HashMap<String, SortStrategy>();
        mpv_HashMapStrategies.put("Even Sort", mpv_evenSort);
        mpv_HashMapStrategies.put("Odd Sort", mpv_oddSort);
        mpv_HashMapStrategies.put("Standard Sort", mpv_standardSort);
    }

    public ObservableList<Coordinate> launchSort(String p_sortToApply, ObservableList<Coordinate> p_listToSort){
        return mpv_HashMapStrategies.get(p_sortToApply).sort(p_listToSort);
    }
}

      

Through the gui, the user can choose the strategy he wants to use to sort the list. The user can click on the button to start sorting. The call is made through the main class (not shown) to inst_field.mpv_sortContext.sortElements(a_string)

with a string as parameters describing the strategy used. This line is then used in sortElements to select the instance of the strategy that the user wants to apply.

With my implementation, I have on the one hand the client and on the other hand all the code that deals with the strategies (Context, interface and concrete classes). If I want to add a new strategy, I just need to add the initialization of the new strategy in the Context class and the description of this new strategy in the gui so that the user is aware of it.

I know that the implementation I did is also not so good because the Context contains an instance for every possible strategy and because of this I don't need an interface reference, but I find it cleaner than field and client resolution knows about these classes.

Well ... Am I completely wrong? There is something I missed in the "canonical" strategy outline. Is the "canonical" way the only and only way to implement the strategy pattern? or is there a better way to implement this pattern in such a way that only the classes that need to know know about the strategy examples and a way that the new strategy can be added easily?

+3


source to share


4 answers


I searched for discussions on this pattern, but no one answered my question ... how can I implement the strategy pattern to keep it clean.

Your "strategy" is not necessarily impure as you describe it, and I think you might get caught up in the idea of ​​who the customer is. Your client provides an implementation, but may be a required implementation detail. For example, the java RMI ComputeEngine tutorial uses exactly this pattern. The "compute" implementation is provided by the client - since only the client knows how to perform the computation.

More often, however, a strategy is used to allow logic to be customized in some context or to resolve a general context for specific purposes. It also has the advantage of hiding internal structures from clients as needed. Often times, the usage strategy will be customized within the context for this. This can be ensured:

  • Algorithm defining a strategy based on the data being processed
  • An algorithm that determines a strategy based on system state or constraints
  • Configuration file to load implementation ( Class.getResourceAsStream

    ). This is an extension of your class map Context

    (i.e. loading a map from a known location). An example here is that you can provide a "strategy representing the default implementation to use, but allow new implementations as an alternative strategy - for example, defaultXMLParser
  • a controller for the data itself - for example, an Object Type might require a strategy to be used to compute its value.

In the first two points, you can use a factory to get the right strategy. This will localize the implementation choice.



Well ... Am I completely wrong? There is something I missed in the "canonical" strategy outline. Is the "canonical" way the only and only way to implement the strategy pattern? or is there a better way to implement this pattern in such a way that only the classes that need to know know about the strategy examples and a way that the new strategy can be added easily?

I said that you are not mistaken. It really depends on the purpose of using the strategy. When it comes to the internal system, some rules should govern the selection (behind the factory). If it is customizable for any reason, then it should be driven by configuration and management hidden within the context (the class that manages the general logic using the strategy). However, if it depends on user data or behavior, then either the data is the drive of choice within, or you have to accept that the client will have to convey your strategy to you.

Note also that the purpose of this pattern is to eliminate conditional logic while retaining alternative implementations. So, if your strategy forces you to do a lot of conditional logic, you may need to rethink if it clears up your code.

</warandpeace>

+1


source


You keep all the strategies in a heap - that's not good. First, the strategy pattern often provides functionality for a long time or even for the entire duration of the application. Therefore, you do not need any other strategy than the chosen one. So, if you have a very large number of very large strategies, you will have a lot of objects in the heap that you don't need.

Also, do not forget that you can initiate your strategy with different parameters, in your case you have frozen objects and you cannot change them.



But don't look at every panther as an axiom. You can change and use it however you want and how you need. Patterns are basic patterns, good practices, etc., but each one may not be perfect for all solutions.

+2


source


Your first implementation is completely in line with the pattern definition (following the classic design patterns - Elements of Reusable Object-Oriented Software), and IMHO you are assigning goals to it that it just never had. The key point you may be missing is what Context

contains (or can receive from the environment) data that is transferred to encapsulated ConcreteStrategy

without collaboration or client knowledge. In other words, the client knows what Strategy

he wants to apply, but not the data in Context

. This is for easy separation of concerns / decoupling.

Adapting these ideas to your first example, he might read:

public interface Strategy {
    public void runOn(Context context);
}
public class ConcreteStrat1 implements Strategy {
    public void runOn(Context context) { ... }
}
public class ConcreteStrat2 implements Strategy {
    public void runOn(Context context) { ... }
}
public class Context {
    private Strategy strategy;
    private InformationPiece1 ip1;
    private InformationPiece2 ip2;
    private InformationPiece3 ip3;
    ...
    // These are the "ContextInterface()" methods: ways for the Strategy and other clients to interact with the Context
    public InformationPiece1 getIP1() { return this.ip1 ; }
    public void setIP1(InformationPiece1 ip1) { this.ip1= ip1; }
    public InformationPiece2 getIP2() { return this.ip2 ; }
    public void setIP2(InformationPiece2 ip2) { this.ip2= ip2; }
    public InformationPiece3 getIP3() { return this.ip3 ; }
    public void setIP3(InformationPiece3 ip3) { this.ip3= ip3; }
    ...
    public Context(Strategy strategy){
        this.strategy= strategy ;
    }
    // This operation can be carried out according to a configurable Strategy
    public void doSomething() {
        this.strategy.runOn( this );
    }
    // This other doesn't.  Or maybe it does, but with a second category of configurable Strategy's
    public void doAnotherThing() {
        ...
    }
}
public class Client {
    public void main(Strings[] args) {
        Context cx;
        // Decide with what Strategy to "configure" cx.
        if( args[0].equalsIgnoreCase("A") )
            cx= new Context( new ConcreteStrat2() );
        else
            cx= new Context( new ConcreteStrat1() );
        // Populate cx.
        new CIBuilder(cx).buildFrom("Maybe a file name? User interaction anyone?") ;
        // Pass cx to another client, which would eventually call cx.doSomething().
        // This client doesn't need to know what Strategy will be called in turn by cx.doSomething().
        // In fact, it doesn't need to know that cx.doSomething() is implemented using the Strategy Pattern at all!
        new DoesntKnowAboutStrategiesNorNeedsTo().process(cx) ;
    }
}

      

Thus, we clearly share roles and responsibilities:

  • main()

    selects Strategy

    based on user input (or maybe later based on properties file, very easy to change). He is aware of the various available Strategy

    and that Context

    takes one to design, but nothing more. From this point of view, it main()

    is a client of the template Strategy

    , but not much <<20>.
  • Context

    is a classic class capable of storing information and performing operations. However, one of these operations can be performed in several different ways ( Strategy

    s). Also, he Context

    doesn't know how many different strategies exist or how they work. In fact, if there is a new one Strategy

    , there is Context

    no need to change at all (if the original design of its interfaces is good nonetheless).
  • DoesntKnowAboutStrategiesNorNeedsTo

    is completely isolated from Strategy

    s, as well as the process of its selection. He only knows the interfaces Context

    , not their implementation. Even if it doSomething()

    was originally implemented without a template Strategy

    , DoesntKnowAboutStrategiesNorNeedsTo

    would not require a change after the change. This is the main client Context

    , but not the template Strategy

    at all.
0


source


It seems to me that you are looking for a design pattern where the main issue is choice Strategy

, not objects that can be customized with one. If this is the case, the most appropriate pattern is Chain of Responsibility

. This is similar to your second example. However, it is much more modular and extensible given that everyone Handler

(the equivalent in this pattern Strategy

) is given the opportunity to decide for themselves (if it can be applied to data or not).

Now, if the goal solves the best Strategy

(as opposed to the first one, which can work with data), the relatively simple option that I used (and I am sure there are many in front of me) is to define a cost criterion. Good examples are runtime, maximum memory that can be used, or a combination of both. Everyone Handler

/ Strategy

should know how to quickly estimate this value for data. Once this basic concept is defined, there are two main implementation alternatives:

  • Define a central controller similar Context

    to the second example with the Handler

    / Strategy

    s directory . Maybe predefined, or maybe customized in some way. This controller has everyone Handler

    who evaluates the data and estimates the costs, and names the one with the minimum to perform the real processing. This is really too much of a spirit Chain of Responsability

    to consider as an option, but wanted to point it out due to its similarity to your original code. Another option is to force the controller to estimate the cost for each Handler

    , but in this case the problems are not separated so that the communication between the components yields tremendous results.

  • Do Handler

    cooperate in finding not the first one that can process Request

    (data), but finding the best one according to the cost.

Below is the generic code for alternative 2 that presents numerous benefits Chain of Responsibility

(as well as a relatively small number of cons).

public abstract class Handler {
    private Handler successor ;
    public void setSuccessor(Handler successor) { this.successor= successor ; }
    public abstract double estimateCostFor(Information info) ;
    public abstract void doProcess(Information info) ;
    public boolean process(Information info) {
        return this.processIfBetterThan(Double.MAX_VALUE,info);
    }
    public boolean processIfBetterThan(double callersCost,Information info) {
        double myCost= this.estimateCostFor(info) ;
        double minCostSoFar= Math.min(callersCost,myCost) ;
        boolean informationProcessed= false ;
        if( this.successor != null )
            informationProcessed= this.successor.processIfBetterThan(minCostSoFar,info) ;
        if( ! informationProcessed && myCost <= minCostSoFar ) {
        // In cases like this, I prefer <= to == especially when dealing with floating point variables.
        // Much safer!
            this.doProcess(info);
            informationProcessed= true ;
        }
        return informationProcessed ;
    }
}
public class ConcreteHandler1 implements Handler {
    public double estimateCostFor(Information info) { ... }
    public void doProcess(Information info) { ... }
}
public class ConcreteHandler2 implements Handler {
    public double estimateCostFor(Information info) { ... }
    public void doProcess(Information info) { ... }
}
public class ConcreteHandler3 implements Handler {
    public double estimateCostFor(Information info) { ... }
    public void doProcess(Information info) { ... }
}
public class Client {
    public void main(Strings[] args) {
        // Setup chain of responsibility
        Handler startOfChain= new ConcreteHandler1() ;
        Handler h2= new ConcreteHandler2() ;
        startOfChain.setSuccessor( h2 );
        Handler h3= new ConcreteHandler3() ;
        h2.setSuccessor( h3 );
        // Obtain Information to process
        Information myInfo= ... ;
        // Process it with the best Handler/Strategy
        startOfChain.process(info);
    }
}

      

0


source







All Articles