Strategy Design Pattern Implementation using Lambda
Recently I tested a complicated module with many different workflows . For different orders, they will go through various workflow. This is a typical scenario of using Strategy Design Pattern, I had implemented this before but this time I use JAVA 8 lambdar to refactor it.
To simplify the problem, I use following Student class as example.
public class Student {
private String name;
private double gpa;
private double writtingScore;
public double getGpa() {
return gpa;
}
public void setGpa(float gpa) {
this.gpa = gpa;
}
public double getWrittingScore() {
return writtingScore;
}
public void setWrittingScore(float writtingScore) {
this.writtingScore = writtingScore;
}
public Student(String name, double gpa, double writtingScore){
this.name = name;
this.gpa = gpa;
this.writtingScore = writtingScore;
}
}
At beginning, we want to select students whose GPA is over 3.5, then we want to select students whose writing score is over 3.0, and later we want to find students with both GPA is over 3.5 and writing score is over 3.0. If you are familiar with Strategy Design Pattern, you should be familiar with following implementation.
Firstly, define a predictor interface.
public interface IPredictor {
public boolean test(Student student);
}
Then, for each select critera, define a specific Predictor class to implement this IPredictor interface. Take the GPA select criteria for example:
public class GPAPredictor implements IPredictor {
private double gpa;
public GPAPredictor(double gpa){
this.gpa = gpa;
}
@Override
public boolean test(Student student) {
return student.getGpa() >= this.gpa;
}
}
To iterate a group of Students, we can implement like following:
public static List<Student> filtStudent(List<Student> students, IPredictor predictor){
List<Student> results = new ArrayList<>();
for(Student student: students){
if(predictor.test(student)){
results.add(student);
}
}
return results;
}
Now to test as following. it looks good enough, right? Actually this is what JAVA often got complained, it is TOO VERBOSE.
@Test
public void testBehaviorParameterize(){
List<Student> students = new ArrayList<>();
students.add(new Student("Tom", 3.8, 2.8));
students.add(new Student("Tom", 3.9, 2.9));
students.add(new Student("Tom", 2.9, 3.5));
students.add(new Student("Tom", 3.6, 3.1));
assertEquals(filtStudent(students, new GPAPredictor(3.5)).size(), 3);
assertEquals(filtStudent(students, new WrittingPredictor(3.0)).size(), 2);
How to make it neat and simple? Now we have lambda from JAVA 8. I still keep the IPredictor interface, but don’t need each implementation class any more. IPredictor is a typical functional interface. In the test, I am writing as following:
assertEquals(filtStudent(students, (Student student)-> student.getGpa() >= 3.5).size(), 3); assertEquals(filtStudent(students, (Student student)-> student.getWrittingScore() >= 3.0).size(), 2); assertEquals(filtStudent(students, (Student student)-> student.getWrittingScore() >= 3.0 && student.getGpa() >= 3.0).size(), 1);
Actually we can make this design be more general. yeah, generic makes our design be more general 🙂 Now I update the interface as following. Actually we can use the java.util.function.Predicate interface directly if you want. If the concept of functional interface is too difficult for you to understand, please go ahead to use following interface.
public interface IPredictor<T> {
public boolean test(T t);
}
And the iteration method as following:
public static <T> List<T> filtStudent(List<T> list, IPredictor<T> predictor){
List<T> results = new ArrayList<>();
for(T t: list){
if(predictor.test(t)){
results.add(t);
}
}
return results;
}
What is the difference between Strategy Design Pattern and Template Method Design Pattern? Strategy Design Pattern chooses algorithm in runtime while Template Method chooses a specific algorithm in compile time. I’ll find an example using Template Method Pattern later.
Another design pattern people often mix up with Strategy Pattern is Decorator Design Pattern. Decorator Pattern is adding additional functions to existing function. Actually Python uses annotation to support Decorator easily.