This example shows how you can create a flexible way to filter a set of data. The program allows you to specify different conditions and ways to apply them to the data. The design uses Predicates, lambdas, and enums to implement the Strategy Pattern.
The program has a list of Person objects that can be filtered in different ways. One way is to filter by age range. Another is by partial name. Any number of filters can be defined. The filters can be applied to the data in two ways: 1) accept items that match any of the criteria and 2) accept items that match all of the criteria.
Suppose the program starts with the following list of Person objects
name=Bob Burgess, age=34
name=Cathy Burgess, age=29
name=Michael Johns, age=15
name=Kim Johnwell, age=27
name=Mary Stevens, age=37
name=Kimberly Johnson, age=21
Then an age range filter is defined:
Enter age range (low high): 12 22
Filter added: Person.age in [12..22]
Since there is only filter defined, both the ANY and ALL strategies filter the list the same way:
name=Michael Johns, age=15
name=Kimberly Johnson, age=21
However, if another filter is added:
Enter name (or part of it): Kim
Filter added: Person.name contains "Kim"
then the list where ANY of the filters match would be
name=Michael Johns, age=15
name=Kim Johnwell, age=27
name=Kimberly Johnson, age=21
whereas the list where ALL of the filters match would be
name=Kimberly Johnson, age=21
The following sections explain the key parts of the design.
The data class on which the filtering will be applied. For simplicity, the name
and age
attributes are made accessible.
The values of this enum define the different strategies for composing the predicates that will be used to filter the data set. Each enum value provides its own implementation of the abstract accumulator()
method. The return type is a BinaryOperator<Predicate<Person>>
that is used in the Stream.reduce()
operation invoked in the Main.matches()
method.
The ANY
enum value uses Predicate.or()
to compose all the filters defined by the user.
The ALL
enum value uses Predicate.and()
to compose all the filters defined by the user.
Prints out all the matching Person
objects that meet the criteria applied per the given MatchStrategy
.
Returns a composed Predicate<Person>
based on the given MatchStrategy
. It uses the strategy's accumulator()
to combine all the user-defined filters into a single composed predicate. For example, ANY
provides an accumulator that will compose/chain all the user-defined filters using the Predicate.or()
method.
The linchpin to all this is the .reduce(strategy.accumulator())
call. This step in the stream processing produces an Optional<Predicate<Person>>
, hence the call to .orElse()
at the end to return a concrete Predicate<Person>
. The argument to .orElse()
is noFilter -> true
which tries to convey the net effect of having no filter since the predicate always returns true
. The name was intentionally chosen to read like a noFilter
flag is being set to true
.
A Static Factory class that provides different methods for producing a Predicate
that captures user-defined parameters. This gives the user the flexibility to define different "customized" filters based on specific values they provide. To add more types of criteria, just add another factory method that encapsulates the predicate logic in a similar fashion as the other factory methods.
Returns a Predicate<Person>
to filter People based on their ages falling within a certain range. The user enters the low and high boundaries of the desired range. When applied, the filter will accept Person
objects with age
that falls in the given range (inclusive of both boundaries).
The returned closed lambda captures the low
and high
values entered by the user.
Returns a Predicate<Person>
to filter People based on their names. The user enters a name or part of a name. When applied, the filter will accept Person
objects in which the entered string appears anywhere in Person.name
.
The returned closed lambda captures the partialName
value entered by the user.
1 - What is the difference between a "closure" and a "lambda": You keep saying "closure" but I don't think it means what you probably think it means. See the answer by SasQ at https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda