I came across a problem when using the strategy pattern. I am implementing a service for creating tasks. This service also resolves the responsible clerk for this task. Resolving the clerk is done by using the strategy pattern because there are different ways of doing this. The point is that every strategy could need different parameters to resolve the clerk.
For example:
interface ClerkResolver { String resolveClerk(String department); } class DefaultClerkResolver implements ClerkResolver { public String resolveClerk(String department) { // some stuff } } class CountryClerkResolver implements ClerkResolver { public String resolveClerk(String department) { // I do not need the department name here. What I need is the country. } }
The problem is that every resolver may depend on different parameters to resolve the responsible clerk. For me this sounds like a design issue in my code. I also tried to have a class as a parameter to keep all values that could be needed by the strategies, like:
class StrategyParameter { private String department; private String country; public String getDepartment() ... } interface ClerkResolver { String resolveClerk(StrategyParameter strategyParameter); }
But to be honest, I am not satisfied with this solution because I have to change the parameter class everytime a strategy needs a new / different argument. And secondly the caller of the strategy must set all parameters because he does not know which strategy will resolve the clerk, therefore he has to provide all parameters (but this isn't that bad).
Again, for me this sounds like a design issue in my code, but I can't find a better solution.
--- EDIT
The main problem with this solution is when creating the task. The task service looks like this:
class TaskService { private List<ClerkResolver> clerkResolvers; Task createTask(StrategyParamter ...) { // some stuff for(ClerkResolver clerkResolver : clerkResolvers) { String clerk = clerkResolver.resolveClerk(StrategyParameter...) ... } // some other stuff } }
As you can see when the TaskService is used, the caller must provide the necessary information to resolve the clerk, i.e. the department name and/or the country, because the TaskService itself doesn't have these information.
When a task has to be created, the caller must provide the StrategyParameter, because they are necessary to resolve the clerk. Again, the problem is, that the caller doesn't have all the information, i.e. he has no knowledge of the country. He can only set the department name. That's why I added a second method to the interface to ensure that the strategy can handle the clerk resolution:
interface ClerkResolver { String resolveClerk(StrategyParameter strategyParameter); boolean canHandle(StrategyParameter strategyParameter); }
At the risk of repeating me, this solution doesn't sound right to me.
So, if anybody has a better solution for this problem I would appreciate to hear it.
Thanks for your comments!
No you can have more than one method on your strategy interface. However, in order for your strategy object to actually use the Strategy pattern, at least one of the method implementations should differ between the different strategies.
The difference simply lies in that they solve different problems: The State pattern deals with what (state or type) an object is (in) -- it encapsulates state-dependent behavior, whereas. the Strategy pattern deals with how an object performs a certain task -- it encapsulates an algorithm.
Strategy pattern is also known as Policy Pattern. We define multiple algorithms and let client application pass the algorithm to be used as a parameter. One of the best example of strategy pattern is Collections.sort() method that takes Comparator parameter.
A pattern-based strategy is essentially the process of a business looking for signs of change, in order to be responsive and reactive at a macro level. Gartner defines pattern-based strategy as the discipline that enables business leaders to inquire, expand, examine and utilize new trade and marketing patterns.
I think there is some confusion about what the task actually is. In my thinking a task is something that is done by a clerk. So you are able to create a task itself without knowing about a clerk.
Based on that task you can choose an appropriate clerk for it. The assignment of the task to the clerk can itself be wrapped to some other kind of task. So a common interface for choosing a clerk would be:
interface ClerkResolver { String resolveClerk(Task task); }
For implementing this kind of clerk resolver you can use the strategy pattern based on the actual type of the task for example.
Congratulations, you discovered one of the shortcomings of strategy pattern:
The strategy pattern can be used to host different algorithms which either have no parameters or the set of parameters for each algorithm is the same. However, it falls short if various algorithms with different sets of parameters are to be used.
Applying it to your specific situation:
public abstract class ClerkResolver { // Role: Algorithm protected Parameter[] parameters; public Parameter[] getParameters() { return parameters.clone(); } abstract String resolveClerk(); }
class CountryClerkResolver extends ClerkResolver { public CountryClerkResolver() { parameters = new Parameter[1]; parameters[0] = new StringParameter("country", "Denmark"); // Default value is 'Denmark' } private String country; @Override String resolveClerk() { country = ((StringParameter) parameters[0]).getValue(); // CountryClerkResolver specific code return country; } }
class DefaultClerkResolver extends ClerkResolver { // Role: ConcreteAlgorithm public DefaultClerkResolver() { parameters = new Parameter[1]; parameters[0] = new StringParameter("department", "someName"); } private String department; @Override public String resolveClerk() { department = ((StringParameter) parameters[0]).getValue(); // DefaultClerkResolver specific code return department; } }
public abstract class Parameter { // Role: Parameter private String name; public String getName() { return name; } public Parameter(String name) { this.name = name; } }
public class StringParameter extends Parameter { // Role: ConcreteParameter private String value; public StringParameter(String name, String value) { super(name); this.value = value; } public void setValue(String value) { this.value = value; } public String getValue() { return value; } }
Example use:
public class Main { public static void main(String... args) { // Role: client ClerkResolver clerk_1 = new CountryClerkResolver(); Parameter[] parameters = clerk_1.getParameters(); StringParameter country = (StringParameter) parameters[0]; // [¤] country.setValue("USA"); // Overwriting default value clerk_1.resolveClerk(); } }
Here is what you would do if you wanted CountryClerkResolver
to take e.g. three parameters instead (one of which is an integer):
First introduce an IntegerParameter
.
public class IntegerParameter extends Parameter { private int value; public IntegerParameter(String name, int value) { super(name); this.value = value; } public void setValue(int value) { this.value = value; } public int getValue() { return value; } }
Now alter the constructor and the method of the strategy:
class CountryClerkResolver extends ClerkResolver { public CountryClerkResolver() { parameters = new Parameter[1]; parameters[0] = new StringParameter( "country", "Denmark" ); // Default value is 'Denmark' parameters[1] = new StringParameter( "newStringParam", "defaultVal"); parameters[2] = new IntegerParameter("newIntegerParam", 9999 ); } private String country; private String newStringParam; private int newIntegerParam; @Override String resolveClerk() { country = ((StringParameter) parameters[0]).getValue(); newStringParam = ((StringParameter) parameters[1]).getValue(); newIntegerParam = ((IntegerParameter) parameters[2]).getValue(); // CountryClerkResolver specific code return country; } }
For a more detailed explanation of the pattern consult the paper.
Benefits:
Algorithm
or Parameter
.Liabilities:
[¤]
), the programmer might mix up the indexes of the parameters
array. (e.g. what if parameters[0]
wasn't country
but, say, continent
)public class Main { public static void main(String... args) { // Role: client ClerkResolver clerk_1 = new CountryClerkResolver(); Parameter[] parameters = clerk_1.getParameters(); // Analyzability suffers because of ugly casting: StringParameter country = (StringParameter) getParameterWithName("country", parameters); country.setValue("USA"); // Overwriting default value clerk_1.resolveClerk(); } private static Parameter getParameterWithName(String paramName, Parameter[] parameters) { for (Parameter param : parameters) if (param.getName().equals(paramName)) return param; throw new RuntimeException(); } }
Parameter[]
can be introduced:import java.util.ArrayList; import java.util.List; public class ParameterList { private final List<Parameter> parameters; public ParameterList(int length) { this.parameters = new ArrayList<>(length); } public void add(Parameter p) { parameters.add(p); } private Parameter getParameterOf(String name) { return parameters.stream() .filter(p -> p.getName().equals(name)) .findFirst() .orElse(null); } // =================================================== ~~~~~~~~~~~~~~~~~~~~~~~~ // The liability of ParameterList is that we have to write a lot of boilerplate getter methods. // However, because most parameter to any strategy class is a primitive type (or String), we don't // have to continiously add new methods; this is thus acceptable. // === A getter for each type of {@code Parameter} is needed ~~~~~~~~~~~~~~~~~~~~~~~~ public StringParameter getStringParameterOf(String name) { return (StringParameter) getParameterOf(name); } public IntegerParameter getIntegerParameterOf(String name) { return (IntegerParameter) getParameterOf(name); } // === A value of each type of {@code Parameter} is needed ~~~~~~~~~~~~~~~~~~~~~~~~ public String getValueOfStringParameter(String name) { return ((StringParameter) getParameterOf(name)).getValue(); } public int getValueOfIntegerParameter(String name) { return ((IntegerParameter) getParameterOf(name)).getValue(); } // =================================================== ~~~~~~~~~~~~~~~~~~~~~~~~ public ParameterList clone() throws CloneNotSupportedException { return (ParameterList) super.clone(); } }
GitHub: all code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With