Think Functional: Rethinking criteria pattern with lambdas

Sujit Kamthe
Being Professional
Published in
5 min readMay 3, 2017

With Java 8 we need to rethink the implementations of most of the design patterns. Lambda expressions prove to be useful in simplifying few design patterns by reducing the boilerplate code required to implement the design pattern. In this post we are going to rethink the criteria design pattern.

Criteria design pattern: classic way

Criteria / Filter / Specification design pattern is widely used to filter out a set of objects based on dynamic filter criteria or business rules which can be chained together using logical operators. It can also be used to determine if the given object satisfies some criteria or business rules.

Consider a set of persons where person is defined by a class Person

public class Person {

private String name;
private String gender;
private String maritalStatus;
private int age;

public Person(String name, String gender, String maritalStatus, int age) {
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
this.age = age;
}

public String getName() {
return name;
}

public String getGender() {
return gender;
}

public String getMaritalStatus() {
return maritalStatus;
}

public int getAge() {
return age;
}
}

Now we want to return a set of persons which satisfy a mentioned criteria.

e.g.

  1. Return all male persons
  2. Return all single persons
  3. Return all male persons and married female persons
  4. Return all married persons whose age is between 20 and 28

and so on…..

The query can be combination of such business rules. Clearly we cannot write if else blocks for each such criteria. It’s not maintainable, readable and may not cover all possible cases.

This can be solved using criteria pattern.

Each simple criteria can be expressed as a Single Abstract Method class (SAM class)

public interface Criteria<T> {
public boolean matches(T candidate);
}

It takes an argument of type T and returns a boolean which indicates if the criteria matches or not. Then we can implement the given interface to create various criteria.

public class MaleCriteria implements Criteria<Person> {
@Override
public boolean matches(Person candidate) {
return candidate.getGender().equals("Male");
}
}

This is fine for a single criteria. But how can we compose or chain multiple criteria using logical operators like AND, OR, NOT etc. For this we will add few more methods in our interface.

public interface Criteria<T> {
public boolean matches(T candidate);

public default Criteria<T> and(Criteria<T> other) {
return new AndCriteria<T>(this, other);
}

public default Criteria<T> or(Criteria<T> other) {
return new OrCriteria<T>(this, other);
}

public default Criteria<T> not() {
return new NotCriteria<T>(this);
}
}

With java 8 we can make use of default methods in interface. Otherwise we will have to create an abstract class to be able to provide a default implementation for our logical methods.

Then we need to create concrete classes for and, or and not methods.

e.g.

public class AndCriteria<T> implements Criteria<T> {
private Criteria<T> leftCondition;
private Criteria<T> rightCondition;

public AndCriteria(Criteria<T> left, Criteria<T> right) {
leftCondition = left;
rightCondition = right;
}

@Override
public boolean matches(T candidate) {
return leftCondition.matches(candidate) &&
rightCondition.matches(candidate);
}
}

Similar implementation can be written for or, not, andNot, orNot etc.

Now we have all the necessary classes to implement the criteria pattern. Time to do the actual work.

Return all male persons

List<Person> persons = new ArrayList<Person>();
persons.stream()
.filter(p -> new MaleCriteria().matches(p))
.collect(Collectors.toList());

Return all male persons and married female persons

List<Person> collect = persons.stream()
.filter((Person p) -> new MaleCriteria()
.or(new FemaleCriteria().and(new MarriedCriteria()))
.matches(p))
.collect(Collectors.toList());

This is analogues to firing SQL queries on a database. Composite criteria can be constructed analogues to the where clause in a SQL query.

Select * from persons where gender = "Male" AND  maritalStatus = "Single";

Typically the composite criteria can be returned from a CriteriaBuilder which handles the complexity of generating composite criteria dynamically based on the requirements.

Criteria design pattern: with Java 8 Lambdas

Can we improve this ? Can all the boilerplate code to create criteria classes be removed ?

Remember the criteria interface is a Single Abstract Method class (SAM class). There is a similar SAM class introduced in Java 8 named Predicate.

public interface Predicate<T> {

boolean test(T t);


default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}


default Predicate<T> negate() {
return (t) -> !test(t);
}


default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

...
}

Does it look familiar ? It looks exactly like the Criteria class we created. Only names are different. So we can use the predefined Predicate class in Java 8, instead of writing our own Criteria class.

But what about the concrete implementations of the Criteria class ? We don’t really need them. The concrete implementation for Predicate would be nothing but a lambda expression.

So the criteria pattern could be re-written as

Predicate<Person> isMale = p -> p.getGender().equals("Male");
persons.stream().filter(isMale).collect(Collectors.toList());
//or as an inline predicatepersons.stream().filter(p -> p.getGender().equals("Male")).collect(Collectors.toList());

Here the lambda expression is a concrete implementation of the Predicate/Criteria.

What about composite criteria? Predicates can be beautifully composed together to create new predicates. Similar to what we defined in Criteria class, there are default methods in Predicate class for this.

Return all male persons and married female persons

Predicate<Person> isMale = p -> p.getGender().equals("Male");
Predicate<Person> isFemale = p -> p.getGender().equals("Female");
Predicate<Person> isMarried = p ->
p.getMaritalStatus().equals("Married");
persons
.stream()
.filter(isMale.or(isFemale.and(isMarried)))
.collect(Collectors.toList());
isMale.or(isFemale.and(isMarried))

This is how, the predicates can be composed. And we don’t have to worry about the scoping of the logical operations. The scoping is automatically taken care.

One thing to note here is that we have written two separate predicates for two different values of gender. What if the set of possible values of a variable is higher ? We don’t want to write a separate predicate for each and every value. This can be fixed by writing a function which takes a value and returns a predicate which compares the value with the actual value of the field. This is known as currying.

Function<String, Predicate<Person>> genderFilter = 
gender -> person -> person.getGender().equals(gender);

Then we can generate required predicate for a specific value (gender) according to our need.

persons
.stream()
.filter(genderFilter.apply("Male"))
.collect(Collectors.toList());

To re-use the composite predicate, we can store the composed predicate in a variable. Then the stored composite predicate can be used to compose another predicates using logical operator methods.

Predicate<Person> isMaleOrMarriedFemale = 
isMale.or(isFemale.and(isMarried));

persons
.stream()
.filter(isMaleOrMarriedFemale)
.collect(Collectors.toList());

With Java 8 functional class Predicate and lambda expression, there is no need to write even a single additional class to implement the criteria design pattern. We have removed almost all the boilerplate code. We should unleash the power of lambda expressions to think on the similar lines while implementing other design patterns too.

Read: Think functional: Advanced builder pattern using lambda

--

--