Behavior parameterization (Strategy pattern) in Java

Important features introduced in Java 8
- Stream APIs
- The mechanism to the passcode to the methods
- The default method in the interface
This article will discuss behavior parameterization code by code.
Behavior Parameterization
It is a mechanism to parameterize the method’s behavior in the code. Similar to the strategy design pattern.
Problem Context
Let’s take an example. Design your application based on requirements.
REQUIREMENT #1
filter green cars.
public static List<Car> filterGreenCars(List<Car> cars) {
List<Car> filteredCars = new ArrayList<>(); for(Car car: cars) {
if ("green".equals(car.getColor())) {
filteredCars.add(car);
}
} return filteredCars;
}
REQUIREMENT #2
Filter red cars
Will you write another method to filter red cars? Of course not. A better approach will be to pass color
as an additional argument.
The method will be changed as follow:
public static List<Car> filterCars(List<Car> cars, String color) {
List<Car> filterdCars = new ArrayList<>(); for (Car car : cars) {
if (color.equals(car.getColor())) {
filterdCars.add(car);
}
} return filterdCars;
}
REQUIREMENT #3
Filter cars with a price lesser than 5L
We have to add another method.
public static List<Car> filterCheaperCars(List<Car> cars) {
List<Car> filteredCars = new ArrayList<>(); for(Car car: cars) {
if (car.getPrice() < 500000) {
filteredCars.add(car);
}
} return filteredCars;
}
This solution is good, but there is a lot of duplication across the filter by color and filter by price methods. In the future, if we have to filter by some other property again we have to duplicate the code.
Code with Abstraction
Declare a car predicate
public interface CarPredicate {
boolean test(Car car);
}
create Green car predicate
class GreenColorCarPredicate implements CarPredicate{
@Override
public boolean test(Car car) {
return "green".equals(car.getColor());
}
}
create cheaper car predicate
class CheaperCarPredicate implements CarPredicate { @Override
public boolean test(Car car) {
return car.getPrice() < 500000;
}
}
Now filter car method can be implemented as follow:
public static List<Car> filterCars(List<Car> cars, CarPredicate p) {
List<Car> filteredCars = new ArrayList<>();
for(Car car : cars) {
if (p.test(car)) {
filteredCars.add(car);
}
}
return filteredCars;
}
CarPredicate represents the behavior for filtering the cars. It prevents the duplication of the code. This is a Strategy Design pattern.
Invoking filter method
Above filterCars
method can be invoked in various ways
Approach #1: Instance of car predicate
// Declare a car predicateclass GreenColorCarPredicate implements CarPredicate{
@Override
public boolean test(Car car) {
return "green".equals(car.getColor());
}
}// Pass instance of the predicate
filterCars(cars, new GreenColorCarPredicate());
Approach #2: Anonymous class
Prevents from creating declaring the class.
filterCars(cars, new CarPredicate() {
@Override
public boolean test(Car car) {
return "green".equals(car.getColor());
}
});
Approach #3: Lambda expression
filterCars(cars, car -> "green".equals(car.getColor()))
Approach #4: Method reference
filterCars(cars, CarUtils::isGreen)
Filter with Streams
cars.stream()
.filter(car -> "green".equals(car))
.collect(Collectors.toList())