Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strategy Pattern with different parameters

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!

like image 708
Daniel Avatar asked Nov 14 '13 08:11

Daniel


People also ask

Can strategy pattern have multiple methods?

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.

What is the difference between pattern and 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.

What is strategy design pattern with example?

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.

What is pattern strategy?

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.


2 Answers

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.

like image 83
SpaceTrucker Avatar answered Sep 18 '22 16:09

SpaceTrucker


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:

enter image description here


enter image description here


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 Algorithmor 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

like image 31
Sebastian Nielsen Avatar answered Sep 18 '22 16:09

Sebastian Nielsen