Applying Arbitrary Custom Filters To A Collection: Chain Of Responsibility
I faced some annoyance of my Randomized Blogroll Plugin the last days: My own blogroll contains some subscriptions to ego searches that I don’t want to publicize on my webpage, since they are
- depressing, and
- not of any public interest.
Getting tired of removing them by hand everytime I update my blogroll, I started to implement filtering. Here’s what I came up with. See my mind at work.
My first sketch was to create a simple class called Filter, containing just one function that gets passed a collection, iterates through it and does what needs to be done. This is PHP 4.x code:
class Filter {
function filter(&$collection) {
// Iterate and apply filter
}
}
However, if I want to apply an additional filter (renaming root category “Subscriptions” to “General”, for example), the collection is stepped through a second time: ugly.
So the next step was to extract the iteration from Filter and move it into the collection class:
class Collection
{
function filter(&$filter) {
foreach ([myvalues] as $item)
$filter->filter($item, $this);
}
}
class Filter {
function filter(&$item, &$collection) {
// apply filter on $item
}
}
However, this doesn’t solve the problem of applying more than one filter, since collection->filter() still only takes one Filter instance. Maybe I should make it accepting an array, too?
No. There is a nice little design pattern at hand that solves this problem: chain of responsibility. This pattern can be applied if
the set of objects that can handle a request should be specified dynamically.
This is excatly what I want to do. Applying this pattern the collection can be left untouched, while I extend the Filter class:
class FilterChain {
/**
* The next chain mamber
*/
var $nextInChain = false;
/**
* Append new member to the end of the chain
*/
function appendToChain($member) {
if ($this->nextInChain == false) {
$this->nextInChain = $member;
} else {
$this->nextInChain->appendToChain($member);
}
}
/**
* Filter
*/
function filter(&$item, &$collection) {
if (
$this->doFilter($item, $collection) &&
$this->nextInChain
) {
$this->nextInChain->filter($item, $collection);
}
}
/**
* Does filtering. Overridden in subclasses.
* Returns TRUE if next filter can be applied, FALSE if not (the item has been removed)
*/
function doFilter(&$item, &$collection) {
return false; // default. Override this.
}
}
class Filter extends FilterChain {
function doFilter(&$item, &$collection) {
// apply filter on $item
// return FALSE if item was removed, otherwise TRUE
}
}
The code is simple. FilterChain delegates filtering to doFilter() which would be virtual protected in C++ or comparable languages. appendToChain() find the last member of the chain and sets its next element property.
The usage is simple, too:
$filter = new SomeFilter();
$filter->appendToChain( new SomeOtherFilter() );
$someCollection->filter($filter);
Reflecting the state of affairs for this plugin, I have customizable input, customizable output, customizable filtering and I’m starting on customizable sorting. Maybe it neds some renaming? What about The most customizable blogroll plugin ever?