Say I have come class that looks like this
class Product {
double Price;
String name;
String category;
}
I have a client facing API that allows users to filter my list of objects based on the name or rating or price. These are implemented through Strategy Pattern like this
interface IFilterStrategy {
boolean apply(Product product);
}
class NameMatchStrategy implements IFilterStrategy {
String name;
public NameMatchStrategy(String name) {
this.name = name;
}
boolean apply(Product product) {
return product.getName().equals(name);
}
}
class PriceMatchStrategy implements IFilterStrategy {
double minVal;
double maxVal;
public StringMatchStrategy(double minVal, double maxVal) {
this.minVal = minVal;
this.maxVal = maxVal;
}
boolean apply(Product product) {
return product.getPrice() >= minVal && product.getPrice() <= maxVal;
}
}
My question is what if I wanted to add a new Strategy, say that matches just the Prefix of a name. So perhaps a user passes "iph
" and I should be able to find a product name that's called "iphone
" and match it with that. I know I can create another NamePrefixMatchStrategy
, but what if in the future I want to extend this Prefix
strategy to say, Category
as well. Like a user could pass in "ph
" and I should find the category "phone
".
The brute force solution would be to just create a CategoryPrefixMatchStrategy
, but the number of my strategies would quickly become hard to maintain.
What's the best design pattern to get around this? Should I create a generic StringMatchStrategy or something, I'm just not sure how to incorporate this into my current design.
You could create interfaces with default methods. So you can reuse the code, but you still have the problem with the number of strategies.
abstract class ExactMatchStrategy implements IFilterStrategy {
String value;
public ExactMatchStrategy(String value) {
this.value = value;
}
public boolean apply(Product product) {
return getFieldValue(product).equals(value);
}
public abstract String getFieldValue(Product product);
}
class ExactNameMatchStrategy extends ExactMatchStrategy {
public ExactNameMatchStrategy(String name) {
super(name);
}
public String getFieldValue(Product product) {
return product.getName();
}
}
class ExactCategoryMatchStrategy extends ExactMatchStrategy {
public ExactCategoryMatchStrategy(String category) {
super(category);
}
public String getFieldValue(Product product) {
return product.getCategory();
}
}
Or with the new Java function package as follows:
class ExactMatchStrategy implements IFilterStrategy {
String value;
Function<Product, String> getField;
public ExactMatchStrategy(String value, Function<Product, String> getField) {
this.value = value;
this.getField = getField;
}
public boolean apply(Product product) {
return getField.apply(product).equals(value);
}
}
class ExactNameMatchStrategy extends ExactMatchStrategy {
public ExactNameMatchStrategy(String name) {
super(name, (p) -> p.getName());
}
}
class ExactCategoryMatchStrategy extends ExactMatchStrategy {
public ExactCategoryMatchStrategy(String category) {
super(category, (p) -> p.getCategory());
}
}
Then you could also instantiate inline
ExactMatchStrategy exactNameMatcher = new ExactMatchStrategy(name, (p) -> p.getName());
ExactMatchStrategy exactCategoryMatcher = new ExactMatchStrategy(category, (p) -> p.getCategory());