Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Strategy pattern - can I delegate strategies instantiation in the Context class?

I am currently learning design patterns on my own. As I studied the Strategy pattern I found something that looks strange for me. I looked for discussions on this pattern but none answered my question... which is how can I implement the Strategy pattern to let it be clean, to maintain encapsulation and to make adding a new strategy easy. To explain my problem here is 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 is going on, but I feel strange to let the client knows something about the different strategies that can be applied. For me it would be cleaner to let Context instantiate the different strategies and not Client, since Context deals with strategies it should (at least in my mind) be the only one to be able to instantiate the strategy.

I implemented a little example using JavaFx with some differences with the code above:

I have a class Field that instantiates a list of coordinates, this class has a method to sort the list. The method that sorts the list of coordinates is the 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 let the concrete strategies inherited from this 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;
    }
}

The concrete classes just return a list that contains all the coordinates that have 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 a user can choose the strategy that he wants to use to sort the list. The user can click on a button to launch a sort. A call is done through the main class (not shown) to inst_field.mpv_sortContext.sortElements(a_string) with a string as parameters that describes the strategy to use. This string is then used in sortElements to select the instance of the strategy that the user wants to apply.

With my implementation I have on one side the client and on the other side all the code that deals with Strategies (Context, interface and concrete classes). If I want to add a new strategy I just have to add an instanciation of the new strategy in the Context class and a description of this new strategy in the gui to let the user knows about it.

I am aware that in the implementation I done is also not so nice because Context contains an instance for each possible strategy and because of that I don't need a reference to the interface, but I find it cleaner than letting the Field and the client knows about these classes.

Well... Am I totally wrong? Is there something I missed in the "canonical" strategy pattern. Is the "canonical" way the one and only way to implement the Strategy pattern? or Is there a better way to implement this pattern in a way that only the classes which should know are aware of the strategy instances and in a way that adding a new strategy can be easily done?

like image 272
OLee Csobert Avatar asked Dec 01 '14 16:12

OLee Csobert


Video Answer


2 Answers

I looked for discussions on this pattern but none answered my question... which is how can I implement the Strategy pattern to let it be clean

Your "strategy" isn't necessarily unclean, as you describe it and I think you may be getting bogged down with the idea of who the client is. Your client is providing the implementation to use but that may be a necessary implementation detail. For instance, the java RMI tutorial's ComputeEngine essentially uses just this pattern. The "compute" implementation is passed by the client - as only the client knows the computation to execute.

However, more commonly, the strategy is used to provide a way for logic to be made configurable within some context or to allow a common context to be tailored to specific uses. It also has the benefits of hiding internal structures from clients as required. Often to do this, the strategy to use will be configured internally to the context. This may be provided by:

  • An algorithm that determines the strategy based on the data to be processed
  • An algorithm that determines the strategy based on system state or constraints
  • A configuration file enabling the implementation to be loaded (Class.getResourceAsStream). This is an extension to your Context class's map (i.e. load the map from a well-known location). An example here is that you may provide a "strategy representing a default implementation to use but allow new implementations to be provided as an alternative strategy - e.g. a defaultXMLParser
  • a controller for the data itself - e.g. An object type may stipulate that a certain strategy be used to calculate its value.

For the first two points above, you could consider using a factory to derive the correct strategy. This would keep the implementation selection concern localised.

Well... Am I totally wrong? Is there something I missed in the "canonical" strategy pattern. Is the "canonical" way the one and only way to implement the Strategy pattern? or Is there a better way to implement this pattern in a way that only the classes which should know are aware of the strategy instances and in a way that adding a new strategy can be easily done?

I wold say that you are not wrong. It really depends on the purpose behind the use of the strategy. If it's an internal system concern then some rules should drive the selection (behind a factory). If it is configurable for whatever reason then it should be driven by configuration and the management hidden inside the context (class that manages the overall logic that uses the strategy). However, if it's dependent on user data or behaviour then either the data drives the selection internally or you have to accept that the client will have to pass you your strategy.

Note also that a goal behind this pattern is to remove conditional logic while keeping alternative implementations. So, if your strategy causes you to do lots of conditional logic then you may need to rethink if it clarifies your code.

</warandpeace>

like image 125
wmorrison365 Avatar answered Sep 28 '22 10:09

wmorrison365


You hold all the strategies in heap - it is not good. Firstly, strategy pattern often provides functionality for a long term, or even for all time app is running. So you don't need any another strategy except chosen. So, in case if you have a very big number of very big strategies, you'll hold in heap a lot of objects, that you don't need.

Also don't forget, that you can init your strategy with different parameters, in your case you have frozen objects and you can't modify them.

But don't look on each patter as axiom. You can modify and use it how do you want and how do you need. Patterns are main models, good practices etc, but each one can't be perfect for all solutions.

like image 37
Orest Savchak Avatar answered Sep 28 '22 10:09

Orest Savchak