Behavior parameterization (Strategy pattern) in Java

Ajay Yadav
2 min readJan 9, 2022

--

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())

--

--