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?