Using Filter Design Pattern In Java
How to use filter design pattern in Java—also known as Criteria Design Pattern, which is a structural pattern that combines multiple criteria to obtain single criteria.
Join the DZone community and get the full member experience.
Join For FreeLets discuss another very useful design pattern named - Filter Design Pattern.
Filter Design Pattern
- The Filter Design Pattern also known as Criteria Design Pattern.
- The Filter Design Pattern is a design pattern that allows developers to filter a set of objects using different criteria/filter condition and chaining them in a decoupled way through logical operations.
- The Filter Design Pattern is a structural pattern which can combines multiple criteria to obtain single criteria.
- With the use of Lambda Expressions in Java 8, it is actually very simple to use without even thinking this as a design pattern. But, if we are aware of the concept of filtering, we will have better understanding and command over the implementation of the code.
- There are two different ways of creating filters
- Filter for Entire collection.
- Filter for single member of the collection.
- I will try to highlight use of lambda for filtering as well under this article. But this article is not about Java Lambda Expressions. Based on the viewer's response I may write another article to cover basics of lambdas as well.
To come straight to the Filtering Pattern on a collection of objects, let's use the Employee class I have used in the article of Builder Design Pattern. It's not required to use a builder at all for filtering. All we need is a group of objects to filter. I am using just to save some time in writing a good POJO class to make a collection of and do some good examples of filtering. I hope you understood.
Employee Collection Filter using Filter Design Pattern
Here's the code for the Employee class:
package org.trishinfotech.filter;
public class Employee {
private int empNo;
private String name;
private Gender gender;
private Deptt depttName;
private int salary;
private int mgrEmpNo;
private String projectName;
public Employee(EmployeeBuilder employeeBuilder) {
if (employeeBuilder == null) {
throw new IllegalArgumentException("Please provide employee builder to build employee object.");
}
if (employeeBuilder.empNo <= 0) {
throw new IllegalArgumentException("Please provide valid employee number.");
}
if (employeeBuilder.name == null || employeeBuilder.name.trim().isEmpty()) {
throw new IllegalArgumentException("Please provide employee name.");
}
this.empNo = employeeBuilder.empNo;
this.name = employeeBuilder.name;
this.gender = employeeBuilder.gender;
this.depttName = employeeBuilder.depttName;
this.salary = employeeBuilder.salary;
this.mgrEmpNo = employeeBuilder.mgrEmpNo;
this.projectName = employeeBuilder.projectName;
}
public int getEmpNo() {
return empNo;
}
public String getName() {
return name;
}
public Gender getGender() {
return gender;
}
public Deptt getDepttName() {
return depttName;
}
public int getSalary() {
return salary;
}
public int getMgrEmpNo() {
return mgrEmpNo;
}
public String getProjectName() {
return projectName;
}
public String toString() {
// StringBuilder class also uses Builder Design Pattern with implementation of
// java.lang.Appendable interface
StringBuilder builder = new StringBuilder();
builder.append("Employee [empNo=").append(empNo).append(", name=").append(name).append(", gender=")
.append(gender).append(", depttName=").append(depttName).append(", salary=").append(salary)
.append(", mgrEmpNo=").append(mgrEmpNo).append(", projectName=").append(projectName).append("]");
return builder.toString();
}
public static class EmployeeBuilder {
private int empNo;
protected String name;
protected Gender gender;
protected Deptt depttName;
protected int salary;
protected int mgrEmpNo;
protected String projectName;
public EmployeeBuilder() {
super();
}
public EmployeeBuilder empNo(int empNo) {
this.empNo = empNo;
return this;
}
public EmployeeBuilder name(String name) {
this.name = name;
return this;
}
public EmployeeBuilder gender(Gender gender) {
this.gender = gender;
return this;
}
public EmployeeBuilder depttName(Deptt depttName) {
this.depttName = depttName;
return this;
}
public EmployeeBuilder salary(int salary) {
this.salary = salary;
return this;
}
public EmployeeBuilder mgrEmpNo(int mgrEmpNo) {
this.mgrEmpNo = mgrEmpNo;
return this;
}
public EmployeeBuilder projectName(String projectName) {
this.projectName = projectName;
return this;
}
public Employee build() {
Employee emp = null;
if (validateEmployee()) {
emp = new Employee(this);
} else {
System.out.println("Sorry! Employee objects can't be build without required details");
}
return emp;
}
private boolean validateEmployee() {
return (empNo > 0 && name != null && !name.trim().isEmpty());
}
}
}
I have added a gender field and also changed depttName to enum for writing filter conditions clearly. So let's define the enums as well.
Here's the code for Gender enum:
xxxxxxxxxx
package org.trishinfotech.filter;
public enum Gender {
MALE, FEMALE;
}
Here's the code for Deptt enum:
xxxxxxxxxx
package org.trishinfotech.filter;
public enum Deptt {
ENG, QA, HR, SUPPORT, IT, ADMIN
}
Example 1: By Using Filter For Entire Collection
In this way we accept the entire collection and return back the collection by eliminating the members which are not applicable as per the filter condition.
Now, lets write an interface named Filter to implement different filter condition or criteria:
xxxxxxxxxx
package org.trishinfotech.filter;
import java.util.List;
public interface Filter {
public List<Employee> apply(List<Employee> employees);
}
Here's the code for MaleFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example1;
import java.util.ArrayList;
import java.util.List;
import org.trishinfotech.filter.Employee;
import org.trishinfotech.filter.Gender;
public class MaleFilter implements Filter {
public List<Employee> apply(List<Employee> employees) {
// implementing in old way
List<Employee> filteredEmployees = new ArrayList<Employee>();
if (employees != null) {
for (Employee employee : employees) {
if (Gender.MALE.equals(employee.getGender())) {
filteredEmployees.add(employee);
}
}
}
return filteredEmployees;
}
}
Here's the code for EngFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example1;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.trishinfotech.filter.Deptt;
import org.trishinfotech.filter.Employee;
public class EngFilter implements Filter {
public List<Employee> apply(List<Employee> employees) {
List<Employee> filteredEmployees = new ArrayList<Employee>();
// implementing using lambda expressions
if (employees != null) {
filteredEmployees.addAll(employees.stream().filter(employee -> Deptt.ENG.equals(employee.getDepttName()))
.collect(Collectors.toList()));
}
return filteredEmployees;
}
}
Lets write two more filter for combining any two filter via 'And Condition' or 'Or Condition'.
Here's the code for AndFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example1;
import java.util.List;
import org.trishinfotech.filter.Employee;
public class AndFilter implements Filter {
private Filter filter;
private Filter anotherFilter;
public AndFilter(Filter filter, Filter anotherFilter) {
this.filter = filter;
this.anotherFilter = anotherFilter;
}
public List<Employee> apply(List<Employee> employees) {
List<Employee> firstFilteredEmployees = filter.apply(employees);
List<Employee> secondFilterEmployees = anotherFilter.apply(firstFilteredEmployees);
return secondFilterEmployees;
}
}
Here's the code for OrFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example1;
import java.util.ArrayList;
import java.util.List;
import org.trishinfotech.filter.Employee;
public class OrFilter implements Filter {
private Filter filter;
private Filter anotherFilter;
public OrFilter(Filter Filter, Filter anotherFilter) {
this.filter = Filter;
this.anotherFilter = anotherFilter;
}
public List<Employee> apply(List<Employee> employees) {
List<Employee> firstFilteredEmployees = filter.apply(employees);
List<Employee> secondFilterEmployees = anotherFilter.apply(firstFilteredEmployees);
// now lets make or Filter.
// first copy all first Filter filtered employees.
// now add all second Filter filtered employees which are NOT already in the list
// via first Filter employees.
List<Employee> orFilteredEmployees = new ArrayList<Employee>(firstFilteredEmployees);
secondFilterEmployees.removeAll(firstFilteredEmployees);
orFilteredEmployees.addAll(secondFilterEmployees);
return orFilteredEmployees;
}
}
Now lets write Main class to execute and test the output:
xxxxxxxxxx
package org.trishinfotech.filter.example1;
import java.util.Arrays;
import java.util.List;
import org.trishinfotech.filter.Deptt;
import org.trishinfotech.filter.Employee;
import org.trishinfotech.filter.Gender;
public class Main {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee.EmployeeBuilder().empNo(101).name("Brijesh").gender(Gender.MALE).depttName(Deptt.ENG)
.salary(140000).projectName("Builder Pattern").build(),
new Employee.EmployeeBuilder().empNo(102).name("Racheal").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(90000).projectName("Factory Pattern").build(),
new Employee.EmployeeBuilder().empNo(103).name("Kim").gender(Gender.MALE).depttName(Deptt.HR)
.salary(150000).projectName("Editorial").build(),
new Employee.EmployeeBuilder().empNo(104).name("Micheal").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(80000).projectName("Decorator Pattern").build(),
new Employee.EmployeeBuilder().empNo(105).name("Martin").gender(Gender.MALE).depttName(Deptt.SUPPORT)
.salary(65000).projectName("Web Management").build(),
new Employee.EmployeeBuilder().empNo(106).name("Pierce").gender(Gender.MALE).depttName(Deptt.HR)
.salary(130000).projectName("Audit").build(),
new Employee.EmployeeBuilder().empNo(107).name("Anjali").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(60000).projectName("State Pattern").build(),
new Employee.EmployeeBuilder().empNo(108).name("Angelina").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(70000).projectName("Flyweight Pattern").build(),
new Employee.EmployeeBuilder().empNo(109).name("Hemant").gender(Gender.MALE).depttName(Deptt.HR)
.salary(170000).projectName("Audit").build(),
new Employee.EmployeeBuilder().empNo(110).name("Mike").gender(Gender.MALE).depttName(Deptt.IT)
.salary(150000).projectName("Networking").build());
System.out.println("Print all employees...");
printEmployees(employees);
List<Employee> maleEmployees = (new MaleFilter().apply(employees));
System.out.println("Print all Male employees...");
printEmployees(maleEmployees);
List<Employee> maleEngEmployees = (new AndFilter(new MaleFilter(), new EngFilter()).apply(employees));
System.out.println("Print all Male And ENG employees...");
printEmployees(maleEngEmployees);
}
private static void printEmployees(List<Employee> employees) {
System.out.println("======================================================================");
employees.stream().forEach(employee -> System.out.println(employee));
System.out.println("======================================================================");
}
}
Below is the output:
xxxxxxxxxx
Print all employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=102, name=Racheal, gender=FEMALE, depttName=ENG, salary=90000, mgrEmpNo=0, projectName=Factory Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=104, name=Micheal, gender=FEMALE, depttName=ENG, salary=80000, mgrEmpNo=0, projectName=Decorator Pattern]
Employee [empNo=105, name=Martin, gender=MALE, depttName=SUPPORT, salary=65000, mgrEmpNo=0, projectName=Web Management]
Employee [empNo=106, name=Pierce, gender=MALE, depttName=HR, salary=130000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=107, name=Anjali, gender=FEMALE, depttName=ENG, salary=60000, mgrEmpNo=0, projectName=State Pattern]
Employee [empNo=108, name=Angelina, gender=FEMALE, depttName=ENG, salary=70000, mgrEmpNo=0, projectName=Flyweight Pattern]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
Print all Male employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=105, name=Martin, gender=MALE, depttName=SUPPORT, salary=65000, mgrEmpNo=0, projectName=Web Management]
Employee [empNo=106, name=Pierce, gender=MALE, depttName=HR, salary=130000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
Print all Male And ENG employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
======================================================================
Example 2: By Using Filter For Single Member of the Collection
In this way we accept a member of the collection in the filter and validate it to know whether the member is satisfying the filter condition or criteria or not.
Here's the code for Filter interface:
xxxxxxxxxx
package org.trishinfotech.filter.example2;
import org.trishinfotech.filter.Employee;
public interface Filter {
public boolean apply(Employee employees);
}
Here's the code for MaleFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example2;
import org.trishinfotech.filter.Employee;
import org.trishinfotech.filter.Gender;
public class MaleFilter implements Filter {
public boolean apply(Employee employee) {
return (employee != null && Gender.MALE.equals(employee.getGender()));
}
}
Here's the code for MinSalaryFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example2;
import org.trishinfotech.filter.Employee;
public class MinSalaryFilter implements Filter {
private int minSalary;
public MinSalaryFilter(int minSalary) {
super();
this.minSalary = minSalary;
}
public boolean apply(Employee employee) {
return (employee != null && employee.getSalary() >= minSalary);
}
}
Similarly, Here's the for AndFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example2;
import org.trishinfotech.filter.Employee;
public class AndFilter implements Filter {
private Filter filter;
private Filter anotherFilter;
public AndFilter(Filter filter, Filter anotherFilter) {
this.filter = filter;
this.anotherFilter = anotherFilter;
}
public boolean apply(Employee employee) {
boolean firstFilter = filter.apply(employee);
boolean secondFilter = anotherFilter.apply(employee);
return firstFilter && secondFilter;
}
}
And here's the code for OrFilter class:
xxxxxxxxxx
package org.trishinfotech.filter.example2;
import org.trishinfotech.filter.Employee;
public class OrFilter implements Filter {
private Filter filter;
private Filter anotherFilter;
public OrFilter(Filter Filter, Filter anotherFilter) {
this.filter = Filter;
this.anotherFilter = anotherFilter;
}
public boolean apply(Employee employee) {
boolean firstFilter = filter.apply(employee);
boolean secondFilter = anotherFilter.apply(employee);
return firstFilter || secondFilter;
}
}
Simpler! Well yes it is since the collection traversing is moved to client application (Main class in our example)
Now lets write our Main class to execute and test the output:
x
package org.trishinfotech.filter.example2;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.trishinfotech.filter.Deptt;
import org.trishinfotech.filter.Employee;
import org.trishinfotech.filter.Gender;
public class Main {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee.EmployeeBuilder().empNo(101).name("Brijesh").gender(Gender.MALE).depttName(Deptt.ENG)
.salary(140000).projectName("Builder Pattern").build(),
new Employee.EmployeeBuilder().empNo(102).name("Racheal").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(90000).projectName("Factory Pattern").build(),
new Employee.EmployeeBuilder().empNo(103).name("Kim").gender(Gender.MALE).depttName(Deptt.HR)
.salary(150000).projectName("Editorial").build(),
new Employee.EmployeeBuilder().empNo(104).name("Micheal").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(80000).projectName("Decorator Pattern").build(),
new Employee.EmployeeBuilder().empNo(105).name("Martin").gender(Gender.MALE).depttName(Deptt.SUPPORT)
.salary(65000).projectName("Web Management").build(),
new Employee.EmployeeBuilder().empNo(106).name("Pierce").gender(Gender.MALE).depttName(Deptt.HR)
.salary(130000).projectName("Audit").build(),
new Employee.EmployeeBuilder().empNo(107).name("Anjali").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(60000).projectName("State Pattern").build(),
new Employee.EmployeeBuilder().empNo(108).name("Angelina").gender(Gender.FEMALE).depttName(Deptt.ENG)
.salary(70000).projectName("Flyweight Pattern").build(),
new Employee.EmployeeBuilder().empNo(109).name("Hemant").gender(Gender.MALE).depttName(Deptt.HR)
.salary(170000).projectName("Audit").build(),
new Employee.EmployeeBuilder().empNo(110).name("Mike").gender(Gender.MALE).depttName(Deptt.IT)
.salary(150000).projectName("Networking").build());
System.out.println("Print all employees...");
printEmployees(employees);
List<Employee> maleEmployees = applyFilter(new MaleFilter(), employees);
System.out.println("Print all Male employees...");
printEmployees(maleEmployees);
List<Employee> maleEngEmployees = applyFilter(new AndFilter(new MaleFilter(), new MinSalaryFilter(140000)), employees);
System.out.println("Print all Male And Min Salary 140000 employees...");
printEmployees(maleEngEmployees);
// now lets try the same with the help of Java Lambda Expressions...
System.out.println("\n\n\nnow lets try the same with the help of Java Lambda Expressions...");
List<Employee> maleEmployeesUsingLambda = employees.stream()
.filter(employee -> Gender.MALE.equals(employee.getGender())).collect(Collectors.toList());
System.out.println("Print all Male employees using lambda...");
printEmployees(maleEmployeesUsingLambda);
List<Employee> maleEngEmployeesUsingLambda = employees.stream()
.filter(employee -> Gender.MALE.equals(employee.getGender()))
.filter(employee -> Deptt.ENG.equals(employee.getDepttName())).collect(Collectors.toList());
System.out.println("Print all Male And ENG employees using lambda...");
printEmployees(maleEngEmployeesUsingLambda);
}
private static List<Employee> applyFilter(Filter filter, List<Employee> employees) {
return employees.stream().filter(employee -> filter.apply(employee)).collect(Collectors.toList());
}
private static void printEmployees(List<Employee> employees) {
System.out.println("======================================================================");
employees.stream().forEach(employee -> System.out.println(employee));
System.out.println("======================================================================");
}
}
And below is the output:
xxxxxxxxxx
Print all employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=102, name=Racheal, gender=FEMALE, depttName=ENG, salary=90000, mgrEmpNo=0, projectName=Factory Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=104, name=Micheal, gender=FEMALE, depttName=ENG, salary=80000, mgrEmpNo=0, projectName=Decorator Pattern]
Employee [empNo=105, name=Martin, gender=MALE, depttName=SUPPORT, salary=65000, mgrEmpNo=0, projectName=Web Management]
Employee [empNo=106, name=Pierce, gender=MALE, depttName=HR, salary=130000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=107, name=Anjali, gender=FEMALE, depttName=ENG, salary=60000, mgrEmpNo=0, projectName=State Pattern]
Employee [empNo=108, name=Angelina, gender=FEMALE, depttName=ENG, salary=70000, mgrEmpNo=0, projectName=Flyweight Pattern]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
Print all Male employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=105, name=Martin, gender=MALE, depttName=SUPPORT, salary=65000, mgrEmpNo=0, projectName=Web Management]
Employee [empNo=106, name=Pierce, gender=MALE, depttName=HR, salary=130000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
Print all Male And Min Salary 140000 employees...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
now lets try the same with the help of Java Lambda Expressions...
Print all Male employees using lambda...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
Employee [empNo=103, name=Kim, gender=MALE, depttName=HR, salary=150000, mgrEmpNo=0, projectName=Editorial]
Employee [empNo=105, name=Martin, gender=MALE, depttName=SUPPORT, salary=65000, mgrEmpNo=0, projectName=Web Management]
Employee [empNo=106, name=Pierce, gender=MALE, depttName=HR, salary=130000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=109, name=Hemant, gender=MALE, depttName=HR, salary=170000, mgrEmpNo=0, projectName=Audit]
Employee [empNo=110, name=Mike, gender=MALE, depttName=IT, salary=150000, mgrEmpNo=0, projectName=Networking]
======================================================================
Print all Male And ENG employees using lambda...
======================================================================
Employee [empNo=101, name=Brijesh, gender=MALE, depttName=ENG, salary=140000, mgrEmpNo=0, projectName=Builder Pattern]
======================================================================
You know what? by using Java 8 or higher, we even do not need to define our filter at all and can create filter conditions by using Java Predicate as we did in our Main while using lambdas.
Well, that all! I hope this tutorial helped to understand the Filter pattern.
Source Code can be found here: Filter-Design-Pattern-Source-Code
Let me know if you like to have an article on Lambda expressions as well? I will try to write the one if you want. :)
Liked the article? Please don't forget to press that like button. Happy coding!
Need more articles on Design Patterns? Please visit my profile to find more: Brijesh Saxena
Opinions expressed by DZone contributors are their own.
Comments