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.
Luckily, this paper presents an elegant solution:
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:
- [Flexible] Change by addition whenever you want to add a new concrete
Algorithm
or Parameter
.
- You don't have to deal with signatures of the public methods of the algorithm (Strategy) since it doesn't take any parameters; the parameters are to be sat prior to calling the method instead.
Liabilities:
- [Stability] When fetching the parameters (see
[¤]
), the programmer might mix up the indexes of the parameters
array. (e.g. what if parameters[0]
wasn't country
but, say, continent
)
-
- A possible solution to address the stability concern, though at the cost of analyzability, is:
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();
}
}
-
-
- To increase readability, an abstraction for the
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