How to handle business requirement changes using behavior parameterization7 min read
Let’s do some scoop on java behavior parameterization concepts and learn what problem it tackles and how it tackles and why do we need that?
Let’s start off with a little bit of theory on the Software Engineering field and the most difficult problem to tackle in this field is the constant change and more precisely I have to say
Business requirements will change or eventually change is needed before the product is thrown out of the market.
Behavior parameterization is a pattern in software development through which you can able to handle frequent requirement changes. Shortly, it means you’re picking a piece of code and making it available without executing it and which will be executed by the caller and other part of the application.
That is, the method’s behavior is parameterized based on that piece of code. Let’s understand this concept with example to get cleaner understanding.
Let’s start off with something with the filter requirements given by whomsoever you’re building the application. I’m gonna be picking an Employee for a better understanding. Filter requirements from the business came as simple like “I want to get all the employees information from a dev team.”
Lets start out with simple Employee class which holds employee basic details.
public class Employee {
private Long id;
private String name;
private String dept;
private String location;
//Skipping getters and setters
}
Add method to generate some dummy employees which we can use it to our example.
public static List<Employee> generateEmployees() {
List<Employee> listOfEmployees = new ArrayList<>();
Employee employee1 = new Employee();
employee1.setId(1L);
employee1.setName("Employee1");
employee1.setLocation("INDIA");
employee1.setDept("dev");
Employee employee2 = new Employee();
employee2.setId(2L);
employee2.setName("Employee2");
employee2.setLocation("INDIA");
employee2.setDept("test");
Employee employee3 = new Employee();
employee3.setId(3L);
employee3.setName("Employee3");
employee3.setLocation("CHINA");
employee3.setDept("test");
Employee employee4 = new Employee();
employee4.setId(4L);
employee4.setName("Employee4");
employee4.setLocation("CHINA");
employee4.setDept("dev");
listOfEmployees.add(employee1);
listOfEmployees.add(employee2);
listOfEmployees.add(employee3);
listOfEmployees.add(employee4);
return listOfEmployees;
}
1st change: Now, let’s jump into actual logic and fulfill the business requirements. We just have to filter out the employees who are all from the “dev” team. We have written this logic uncountable times in a developer’s lifecycle.
private static List<Employee> getEmployeesFromDev(List<Employee> employees) {
List<Employee> resultList = new ArrayList<>();
for (Employee emp: employees) {
if(emp.getDept().equalsIgnoreCase("dev")) {
resultList.add(emp);
}
}
return resultList;
}
2nd change: Now this looks good and business is happy with the changes and after a few days they come back and ask for the list of employees from the “test” team. Just now realized that
“oh god !! it needs a code change”.
So you gave some thoughts and realized requirements come back later like “Get all employees from some other dept”. So you updated the above method to accept any argument of department name and you will just return the filtered out result to the caller.
Here the caller will be the one responsible for calling the method with the correct department name based on the requirements. So start assuming the getEmployeesFromDev
method comes from the framework you’re developing and the caller is just consuming it. With this change, you’re actually changing the code in the framework and you got to make a release for this code change.
private static List<Employee> getByDeptName(List<Employee> employees,String deptName) {
List<Employee> resultList = new ArrayList<>();
for (Employee emp: employees) {
if(emp.getDept().equalsIgnoreCase(deptName)) {
resultList.add(emp);
}
}
return resultList;
}
List<Employee> employees = generateEmployees();
List<Employee> deptWiseEmployees = getByDeptName(employees,"test");
System.out.println(deptWiseEmployees);
3rd change: As usual, a change from the business team like, I want to do a filter on employees based on department and as well as on the employee location. Again, you thought for some time and finally, you settled that it does as well need code change from the framework because there is no way for the client/caller application to do this without a code change from the framework. So we go back and add a little flag to handle both the requirements in the same method. Something like below,
private static List<Employee> getByDeptName(List<Employee> employees,String deptName, String location, boolean flag) {
List<Employee> resultList = new ArrayList<>();
for (Employee emp: employees) {
if((flag && emp.getDept().equalsIgnoreCase(deptName)) || (!flag && emp.getLocation().equalsIgnoreCase(location))) {
resultList.add(emp);
}
}
return resultList;
}
Now the caller/client would change something like below in his code and really starts wondering “What is this boolean and what is does if I pass true?”
//Get all employees working in China location
List<Employee> deptWiseEmployees = getByDeptName(employees,"","CHINA",false);
//Get only dev team
List<Employee> deptWiseEmployees = getByDeptName(employees,"dev","",true);
Above framework method getByDeptName
really looks ugly and it will never be easy to cope up with the new changes. Where there is a change need, ideally you got to do a code change at framework as well as client side.
GOLDEN TIP: This tip is from the Uncle bob from his book “Code clean”. It goes something like below
Boolean arguments loudly declare that the function does more than one thing. They are confusing and should be eliminated. Function should always do one thing !!
Now coming back to solution or cleaner solution which can cope up with any changes which can come from the client? Lets take a pause and find a way for doing it in abstract way. With java 8, one possible solution is model your selection criteria with employees and returning a boolean based on the argument passes like is employee from India? Is employee from the dev team? etc.
In java 8, this is called as Predicate
, a function that returns a boolean. Lets start by creating a predicate for our employee.
public interface EmployeePredicate {
boolean test(Employee employee);
}
Now lets create some class according to the requirements and implement the EmployeePredicate.
public class DevDeptPredicate implements EmployeePredicate {
@Override
public boolean test(Employee employee) {
return employee.getDept().equalsIgnoreCase("dev");
}
}
public class IndianEmployeePredicate implements EmployeePredicate {
@Override
public boolean test(Employee employee) {
return employee.getLocation().equalsIgnoreCase("INDIA");
}
}
Now we can update our filter employee method to be abstract. So that it will not hold any sort of implementation details of the caller or depend on any other external parameters from the client.
private static List<Employee> filterEmployees(List<Employee> employees, EmployeePredicate ep) {
List<Employee> resultList = new ArrayList<>();
for (Employee employee: employees) {
if(ep.test(employee)) {
resultList.add(employee);
}
}
return resultList;
}
Now this code looks much cleaner and much understandable than all the previous versions !! Now lets see how the caller code looks like,
List<Employee> indianEmployees = filterEmployees(employees, new IndianEmployeePredicate());
List<Employee> devDeptEmployees = filterEmployees(employees, new DevDeptPredicate());
System.out.println(indianEmployees);
System.out.println(devDeptEmployees);
Finally this is what the behaviour parameterization is all about. Able to tell a method to take multiple behaviors as parameters and use them internally to accomplish the different parameters.
This all looks good and you must have seen a pattern here which is used. Strategy pattern is applied here.
The next questions that will come to your mind are like that’s a lot of classes we need to create. If I have a condition to filter employees based on certain attributes, do I need to create a class for these conditions? Then the project will overflow with predicate class which just holds the simple condition.
A valid question and If you have lots of conditions, then we can directly make a predicate class directly while passing the arguments to the filter employee method.
List<Employee> indianEmployees = filterEmployees(employees, (Employee employee) -> employee.getLocation().equalsIgnoreCase("INDIA"));
List<Employee> devDeptEmployees = filterEmployees(employees, (Employee employee) -> employee.getDept().equalsIgnoreCase("dev"));
System.out.println(indianEmployees);
System.out.println(devDeptEmployees);
That’s just great and it is all about your decision which path you choose for designing your application. Create predicate on the fly while passing or you can follow the strategy pattern. For simpler cases, on the fly would work out and when you want to enforce certain code behavior to the whole project, so that team members can follow the same, then strategy pattern will be very useful.
Wow !! That was great scoop and I hope you understood the concept and you liked it.