Reputation: 1508
I have to design entities like Filters, represented by Filter
interface, declaring the apply(Content content)
method, which can be applied to a Content object. Filters can be composed together in chains, similar to workflows, but these are dynamic. For example, if FilterA return X, then I will apply filterB, while receiving result Y will cause to apply FilterC. The chain of the filters is application-specific, and I haven't yet decided how to allow the construction of filter chains.
I would design this behavior in the same way of some workflow frameworks: a manager component iterating over a list of filters, and calling filter.apply(content)
on each filter. But how to allow dynamism like if/else statements?
For now I conceived a Workflow or FilterChain interface, declaring a getNextFilter(previousResult)
. Implementing this interface one can declare an application-specific workflow. But the implementation of Workflow interface will be boring: to keep track of current step (an integer?) and then at each getNextFilter()
invocation, determining which filter will be the next, through a switch/case statement?!?
Which solution could be better? How to declare a chain?
I'm using Java and Spring, so IoC can be used.
Upvotes: 5
Views: 10814
Reputation: 1693
For this case I would try to model the chain and move the responsibility of execution to the chain itself by giving a little bit more of "intelligence" to the nodes. In a way I would consider the nodes as being commands, in the sense that they can execute themselves and, by having a common interface, be able to create composites. (Btw, I'm not a Java programmer, so please forgive me for the possible syntax errors). So, my main design decisions would be:
I would start by defining something like:
public abstract class ChainNode {
public abstract Content apply(Content content);
}
/**
The NullObject of the chain
http://www.oodesign.com/null-object-pattern.html
*/
public class FinalNode extends ChainNode {
public Content apply(Content content) {
return content;
}
}
/** A sample filter */
public class FilterA extends ChainNode {
private ChainNode nextNode;
FilterA(ChainNode nextNode) {
this.nextNode = nextNode;
}
public Content apply(Content content) {
filteredValue = //Apply the filter
return nextNode.apply(filteredValue);
}
}
/** An if-then-else filter */
public abstract class ConditionalFilter extends ChainNode {
private ChainNode trueFilter;
private ChainNode falseFilter;
ConditionalFilter(ChainNode trueFilter, ChainNode falseFilter) {
this.trueFilter = trueFilter;
this.falseFilter = falseFilter;
}
public Content apply(Content content) {
if (this.evalCondition(content)) {
return this.trueFilter.apply(content);
}
else {
return this.falseFilter.apply(content);
}
}
private abstract boolean evalCondition(Content content);
}
Whit this approach what you are doing is turning the control structures into objects and asking them to execute, which even allows you to create a logic different than the standard if-then or switch statements. With this base you could create a chain that has different branching operators, triggering different filtering paths.
Some things to note are:
Content
, which actually allows you to chain one filter after the other. I guess that is true in your requirement, but I'm not sure.apply
method.ConditionalFilter
and redefine the evalCondition
method. I don't know if Java has closures (I think it doesn't), but if it has you could instead add a condition
instance variable and parametrize it with a closure, thus avoiding subclassing for each new condition. Or maybe there is a more accepted workaround for things like this in the Java world, I just don't know :(.content
parameter. If you need more information to make the decision you could have also a context object passed in the apply
method. Depending on your needs this can be a structured object or just a dictionary if you need more flexibility.Finally, regarding the construction of the chains, if the chains are long and complex to build I think a builder here should suit your needs.
HTH
Upvotes: 3
Reputation: 743
Here's example of composite pattern implementing filter chain in php.
$filter = new BookFilter\Chain();
$filter->appendFilter(new BookFilter\Isbn('978-5-8459-1597-9'))
->appendFilter(new BookFilter\Title('Domain Driven', BookFilter\Title::CONTAINS))
->appendFilter(new BookFilter\Publisher('Вильямс', BookFilter\Publisher::EQUALS))
->appendFilter(new BookFilter\Price(100, 10.000))
->appendFilter(new BookFilter\Ebook(true));
$bookCollection = $bookSeachService->findAllByFilter($filter);
Taken from here: http://crazycode.net/blog/6-architecture/10-structural-patterns
Upvotes: 1
Reputation: 8576
For generality I am assuming, that the conditional should decide not only between single filters but between filter chains.
After some thought it seems to me that the Composite Pattern fits quite nicely here.
Component
: your Filter
interfaceLeafs
: concrete filtersComposite
: your "Workflow or FilterChain interface"The ConditionalFilter
could be either a Leaf
or a Composite
.
In both cases, initialized with a compare and two Filter
objects, it can branch on either single filters or filter workflows.
Upvotes: 2
Reputation: 11733
What you need here is the pattern Chain of Responsibility.
Having implemented this many times in Java, my recommendations would be:
The participation mechanism should be an interface, that way you could have specialized agents that do very different things and agree only to receive messages and pass them on according to the interface dictate.
If you want to progressively filter, you can pass the content along, as you show in your question, and each participant can mutate it. I have an open source project on GitHub called scraper that uses Chain of Responsibility to implement chains of agents that extract the parts of the page being scraped that would contain content.
Upvotes: 0
Reputation: 2500
Since you are just wanting to execute the filter chain in order perhaps it would be simplest to implement your filter chain as a List<Filter>
. You could just do a loop over them and execute.
Something like (note this is obviously a quick implementation I've not tried):
public class FilterChainImpl implements FilterChain {
private List<Filter> filterChain = new ArrayList<Filter>();
public addFilter(final Filter f) {
filterChain.add(f);
}
public apply(final Content content) {
Content prevContent = content;
for(Filter f : filterChain) {
prevContent = f.apply(prevContent);
}
}
}
Then you can use this to generically create any filter chain you like. You might use some factory technique for creating the filter chains if you are going to have many different filter chains.
Upvotes: 0