I would say this pattern creates some ambiguity. What should the result be, for instance, if the caller does not call either include()
OR exclude()
? There is no chance to catch this situation statically, so either you have to deal with this ambiguity in your library or throw a runtime exception.
As a suggestion, why not make the return value of the callback an action defining what to do with the element?
If we call what you want to do a Milter (Map + Filter), you might end up with something like this (Don't have a compiler here so this may not be picture-perfect):
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test
{
public static void main(String[] args)
{
Milter.Visitor<Integer> visitor = new Milter.Visitor<Integer>() {
@Override
public MilterAction<Integer> visit(Integer element) {
if (element % 2 == 0) {
return MilterAction.exclude();
} else {
return MilterAction.include(element + 2);
}
}
};
Milter<Integer> oddsPlus2Milter = new Milter<Integer>(visitor);
List<Integer> sourceList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
oddsPlus2Milter.run(sourceList).forEach(System.out::println);
}
}
class Milter<T> {
public interface Visitor<I> {
MilterAction<I> visit(I element);
}
private Visitor<T> visitor;
Milter (Visitor<T> visitor) {
this.visitor = visitor;
}
List<T> run(List<T> source) {
List<T> milteredResult = new ArrayList<T>();
source.forEach(element -> {
MilterAction<T> visitResult = this.visitor.visit(element);
if (visitResult.shouldInclude()) {
milteredResult.add(visitResult.getValue());
}
});
return milteredResult;
}
}
class MilterAction<T> {
public static <I> MilterAction<I> include(I value) {
return new MilterAction<I>(FilterAction.INCLUDE, value);
}
public static <I> MilterAction<I> exclude() {
return new MilterAction<I>(FilterAction.EXCLUDE, null);
}
public enum FilterAction {
INCLUDE,
EXCLUDE
}
private FilterAction action;
private T value;
MilterAction(FilterAction action, T value) {
this.action = action;
this.value = value;
}
public T getValue() {
return value;
}
public boolean shouldInclude() {
return this.action.equals(FilterAction.INCLUDE);
}
}
This way, your compiler can ensure for you that all consumer's of the Milter class provide it with a visitor that always returns an appropriate action.