Originally published on DZone on June 4, 2018.
The Filterer pattern is a design pattern for Java (and potentially other OO languages with use-site variance only) that helps container-like objects return filtered versions of themselves.
Disclaimer: What I am writing about here is my own idea, but it is quite likely that someone has already come up with it and described it (I was unable to find it, however). If so, please let me know, and I will reference it here.
Problem
Example
Container-Like Object
Let us start with a simple contained type (Item
) and container type (Group
):
interface Item {
String name();
}
interface Group {
List<? extends Item> items();
}
Note that Group
makes use of covariance (? extends Item
).
Filtering in Group
Now, I wanted Group
to have a method that would return a version of itself but filtered by Item
s, so I added a filtered
method:
interface Group {
List<? extends Item> items();
Group filtered(Predicate<? super Item> predicate);
}
Note that here we needed contravariance (? super Item
).
Subinterface of Group
Such interface may look fine, but the problem with this design is that it is not versatile for subtypes of Item
and Group
:
interface ScoredItem extends Item {
int score();
}
interface ScoredGroup extends Group {
@Override
List<? extends ScoredItem> items();
@Override
ScoredGroup filtered(Predicate<? super Item> predicate);
}
As you can see, we overrode filtered
, but the parameter type (Predicate<? super Item>
) had to remain unchanged. Because of type erasure in Predicate
, we cannot overload this method either.
As a result, we are able to create a filtered ScoredGroup
but unable to filter by ScoredItem
s (which makes filtered
method much less useful).
Better Filtering in Subinterfaces
To mitigate this, we could add an extra method with a proper lower bound in Predicate
(e.g. scoreFiltered
):
interface ScoredGroup extends Group {
@Override
List<? extends ScoredItem> items();
ScoredGroup scoreFiltered(Predicate<? super ScoredItem> predicate);
@Override
default ScoredGroup filtered(Predicate<? super Item> predicate) {
return scoreFiltered(predicate);
}
}
Impracticality of Better Filtering in Subinterfaces
But as soon as we add further subinterfaces (e.g. ScoreExplanatoryItem
and ScoreExplanatoryGroup
), it gets totally out of hand:
interface ScoreExplanatoryItem extends ScoredItem {
String explanation();
}
interface ScoreExplanatoryGroup extends ScoredGroup {
@Override
List<? extends ScoreExplanatoryItem> items();
ScoreExplanatoryGroup scoreExplanatoryFiltered(Predicate<? super ScoreExplanatoryItem> predicate);
@Override
default ScoreExplanatoryGroup scoreFiltered(Predicate<? super ScoredItem> predicate) {
return scoreExplanatoryFiltered(predicate);
}
@Override
default ScoreExplanatoryGroup filtered(Predicate<? super Item> predicate) {
return scoreExplanatoryFiltered(predicate);
}
}
Problem Summary
To sum up, since the filtered
method cannot be overriden, we suffer from the following:
- The subinterfaces of
Group
become cluttered, especially as we move down the type hierarchy. - The default methods in those subinterfaces are just boilerplate.
- When accessing the subinterfaces, it may be difficult to decide which method to call to get the desired lower bound on
Predicate
.
Solution
What we need here is an ability to covariantly specify a lower bound of contravariant Predicate
in the subinterfaces of Group
.
In Java, it cannot be done explicitly (i.e. by overriding the filtered
method with a different Predicate
), but fortunately, it can be done by means of a helper interface, which I dubbed Filterer
*.
* In case you wonder whether the word exists, consult Wiktionary.
Introducing Filterer
Interface
We define Filterer
interface as follows:
@FunctionalInterface
interface Filterer<G extends Group, E extends Item> {
G by(Predicate<? super E> predicate);
}
And thanks to this interface, we can redefine Group
as follows:
interface Group {
List<? extends Item> items();
Filterer<?, ?> filtered();
}
Let’s see how this works for the subinterfaces:
interface ScoredGroup extends Group {
@Override
List<? extends ScoredItem> items();
@Override
Filterer<? extends ScoredGroup, ? extends ScoredItem> filtered();
}
interface ScoreExplanatoryGroup extends ScoredGroup {
@Override
List<? extends ScoreExplanatoryItem> items();
@Override
Filterer<? extends ScoreExplanatoryGroup, ? extends ScoreExplanatoryItem> filtered();
}
As you can see, the interfaces remain clean (no extra methods, no boilerplate) because we are able to override filtered
method now. And we do this by employing covariance (? extends
) which sets the lower bound for the contravariant Predicate
in by(Predicate<? super E>)
.
Implementation
So far so good, but how is this design going to impact the implementation of our interfaces?
It turns out the implementation is going to be pretty straightforward:
@Immutable
final class PlainItem implements Item {
private final String name;
// constructor here (or Lombok's @AllArgsConstructor)
// name() method here (or Lombok's @Getter @Accessors(fluent = true))
}
@Immutable
final class PlainGroup implements Group {
// I'm using com.google.common.collect.ImmutableList
private final ImmutableList<PlainItem> items;
// constructor here
// items() method here
@Override
public Filterer<? extends PlainGroup, ? extends PlainItem> filtered() {
return this::filteredGroup; // the best part (instance method reference)
}
private PlainGroup filteredGroup(Predicate<? super PlainItem> predicate) {
return new PlainGroup(filteredItems(predicate));
}
private ImmutableList<PlainItem> filteredItems(Predicate<? super PlainItem> predicate) {
return this.items.stream()
.filter(predicate)
.collect(ImmutableList.toImmutableList());
}
}
As you can see, we have a classic method for providing the filtered version of the PlainGroup
(i.e. filteredGroup
). However, this method has been made private, and is used only as an instance method reference, thus capturing this
object. As a result, we never even have to create a real implementation of the Filterer
interface!
As far as I know (correct my if I am wrong), the extra object creation in filtered
will not exhibit any performance penalty because JVM’s escape analysis will be triggered (as long as the object is consumed right after its creation, of course).
Usage
Just a short (and obvious) code sample showing how this looks when used:
@Test
public void testFiltered() {
ImmutableList<PlainItem> items = ImmutableList.of(
new PlainItem("a"),
new PlainItem("b"),
new PlainItem("ab")
);
PlainGroup group = new PlainGroup(items);
PlainGroup filteredGroup = group.filtered().by(item -> item.name().startsWith("a"));
Assert.assertEquals(2, filteredGroup.items().size());
Assert.assertFalse(filteredGroup.items().contains(items.get(1)));
}
Playing With Filterer
Generic Filterer
For clarity, I defined Filterer
above to depend on Group
and Item
. But note that Filterer
can be made completely generic (i.e. without the ? extends
in type parameters):
@FunctionalInterface
interface Filterer<G, E> {
G by(Predicate<? super E> predicate);
}
Such a generic interface can be reused everywhere then, provided it is used with appropriate upper bounds (? extends
). In order to apply it to Group
, we would write:
interface Group {
List<? extends Item> items();
Filterer<? extends Group, ? extends Item> filtered();
}
Filterer with Syntactic Sugar Methods
On the other hand, if we do not make the interface generic, we can introduce some syntactic-sugar default methods to it:
@FunctionalInterface
interface Filterer<G extends Group, E extends Item> {
G by(Predicate<? super E> predicate);
default G byName(String name) {
return by(item -> item.name().equals(name));
}
}
We can even go as far as creating subtypes of Filterer
with specialized default methods:
@FunctionalInterface
interface ScoredFilterer<G extends ScoredGroup, E extends ScoredItem> extends Filterer<G, E> {
default G byMinScore(int minScore) {
return by(item -> item.score() >= minScore);
}
}
interface ScoredGroup extends Group {
@Override
List<? extends ScoredItem> items();
@Override
ScoredFilterer<?, ?> filtered();
}
As you can see, this pattern is quite flexible.
Summary
I have shown here how one can use a helper interface (dubbed Filterer
) to provide objects using it with convenient means of returning filtered versions of themselves.
I found this design pattern useful on a few occasions now, and I hope you might find it useful too.
Thank you for reading.
See also: Filterer Pattern in 10 Steps.
6 thoughts on “Filterer Pattern”
Interesting approach. However, can you give us a concrete use case in which you used the
Filterer
interface? I mean, which are the differences to use a simple filter method in a stream chain? Am I missing something?Thanks for your comment, Riccardo!
Indeed, after having written this post, I realized I have not justified the introduction of
Filterer
well enough. I then tried to justify it in the comments to the original DZone article, which I quote below:If anything is still unclear, feel free to ask!
Thanks for the response. But, I still don’t catch the utility of this pattern. Are you trying to achieve the same use cases of streams (in terms of filtering operation) without using them?
Thanks, Riccardo. I can now see that the examples I provided are incomplete, vague, and generally too hard to follow.
I started to prepare a minimal complete example (including actual use case, and specifically showing why I chose to apply
Filterer
). But the example is already too long for a comment, so I decided I’ll make it into a short blog post. I’ll let you know when it’s ready (it’s going to take me a couple of days) 🙂Riccardo, sorry that it took so long!
I didn’t expect writing this to be so hard and time-consuming. I’m not entirely satisfied with the result (it’s too long), but hopefully it’ll clarify some things for you: Filterer Pattern in 10 Steps.
If you manage to read it, please let me know what you think 🙂
For reference: discussion on Reddit.