Page 1 of 2
Mechanisms to override behavior
Posted: Tue Feb 06, 2007 6:49 pm
by Ambush Commander
Imagine, for a moment, a message filter system. Each filter processes the input, and passes it on to the next filter, until all the filters have been processed. A way to implement the filter might be a stack:
Code: Select all
$filters = array(
new Filter1(),
new Filter2(),
new Filter3()
);
$input = 'foo';
foreach ($filters as $filter) {
$input = $filter->filter($input);
}
Which works very nicely and is quite compact.
Suppose, however, the $filters array could come from multiple places. Instead, we have:
Code: Select all
$filters = array();
// Module1 (in a different file)
$filters[] = new Filter1();
// Module 2
$filters[] = new Filter2();
// ...
And let's also suppose that we have a new filter Filter1Plus, which is an enhanced version of Filter1's functionality, and thus, needs to disable Filter1. Our original list implementation doesn't allow overriding or disabling of old filters. Thus, it might be in our interest to convert from using a list to using an associative array:
Code: Select all
$filters = array();
// Module1
$filters['Filter1'] = new Filter1();
// Module2
$filters['Filter2'] = new Filter2();
// Module1Plus!
$filters['Filter1'] = false; // or new NullFilter() if you balk at special-case code
$filters['Filter1Plus'] = new Filter1Plus();
This approach too, however, poses troubles. First of all, how are users to name their filters? Off of class names, a namespaced ModuleName-ClassName approach? Generated Unique ID? If we opt for a simplistic free-for-all naming approach, we run the high risk of collisions: what if another user doesn't realize that Filter2 already exists and accidentally overwrites it with their own filter? In it's current state, the system has no way of knowing. Take a more complicated use case: Filter1Plus overrides Filter1, and Filter1PlusPlus also overrides Filter1. How do we figure out that Filter1Plus and Filter1PlusPlus might conflict with each other? If we namespace names, should we automatically enforce it? How would a module refer to another module that it isn't even sure exists (i.e. marking dependencies)? Does overriding precedence stem off of the order the modules are loaded in? So many questions! Perhaps a better one would be:
How would you do it?
If I'm over-engineering the problem, please say so.
Posted: Tue Feb 06, 2007 7:04 pm
by Christopher
It sounds like you are over-engineering -- but I don't really know the problem. I guess my main questions would be about use cases. Are named (and hence overwritable) filters the norm or the exception. I would make the normal use case clen and then add support (e.g. a has() method) to support controlling overwriting.
Posted: Tue Feb 06, 2007 7:09 pm
by Ambush Commander
::looks sheepish:: There is currently only one defined filter, and it is not likely going to be disabled. But possibly... Just trying to be forward thinking! Having no way to remove a filter would be very frustrating for a developer that would possibly want to remove it.
e.g. a has() method
That's not really workable unless I introduce the Visitor pattern. I misrepresented my examples: a module is really a class that you instantiate and stuff inside the full definition: it has no knowledge of the whole.
Posted: Tue Feb 06, 2007 8:19 pm
by Ollie Saunders
You might want to read up on the chain of command pattern.
Regarding the point of overwriting, why not use integer keys?
Posted: Tue Feb 06, 2007 8:26 pm
by Ambush Commander
I'm aware of the chain of command pattern. However, in my situation, all objects are usually going to be called. Are you suggesting that all "filters" have a plug point where a sub-filter may be attached and override their behavior?
Integer keys aren't good because they're not explicit and are difficult to read.
Posted: Tue Feb 06, 2007 8:32 pm
by Ollie Saunders
Arggghh neeeed sleep.
bye
Posted: Tue Feb 06, 2007 9:03 pm
by wei
may be have a FilterManager,
Code: Select all
$filters = new FilterManger();
$filters->add(new Filter1());
...
echo $filters->apply($input);
class FilterManager
{
function apply($value)
{
foreach($this->filters as $filter)
$value = $filter->run($this, $value);
return $value;
}
function getFiltersByType($type)
{
...
}
}
class Filter
{
function run($manager, $value)
{
... do complex filtering, may call $manger to apply more filtering...
}
}
Posted: Tue Feb 06, 2007 9:06 pm
by Ambush Commander
Ahh, I was afraid of that. That's a lot of code for a use-case where there is usually only one filter!
All these future requirements are pure speculation, but I want to make sure I don't lock myself into an API that would be difficult to change should speculation turn to reality.
Posted: Tue Feb 06, 2007 9:10 pm
by wei
you can have a simple filter that is not depedent on any manager class, but it will lose the ability to modify other filters. However, this filter can be decorated to be aware of the manager if need be.
e.g. either pass the manager in filter constructor or make the manager pass it when running the filter (the manager needs to be a little smarter now) so you can trade off complex at construction or when running.
Posted: Tue Feb 06, 2007 9:27 pm
by Ambush Commander
Perhaps. It's a bit bulky, but it may work out nicely. Abstracting the manager certainly is a very good possibility.
Posted: Wed Feb 07, 2007 12:39 am
by alex.barylski
You need to decide how you want to handle overriding filters...
1) Do you use an explicit approach, for example does the caller determine what filter get's replaced/overriden?
2) Do you use an automated/implicit approach. Does each filter have the intelligence to know which filters it is to disable (what arborint has suggested - I think?)?
These are my first thoughts anyways

Posted: Wed Feb 07, 2007 12:42 am
by feyd
Class heritage may be one path to automating the override status.. although that could produce poor behavior in some peoples' eyes.
I think explicit is best for experienced programmers, but is that the audience? Is it logical, from a newb perspective, to override based on heritage?
Posted: Wed Feb 07, 2007 1:21 am
by alex.barylski
Just to add to what feyd said.
Explicit is good in situations where one might need to adjust the code, but this sounds more like a framework, in which case automation is what I would prefer. Explicit coding would require discipline on bahlf of the client developer, making sure not to override something by accident, etc and could lead to hard to diagnose bugs.
Automation could be done two ways:
1) Using a framework approach, where you use a "Manager" type deal as already noted. Using a class inheritence you could possibly do something like...
Code: Select all
$obj->add(new Filter1);
$obj->add(new Filter2);
// ...
function add($obj)
{
// 1) Iterate the array of existing filters
// 2) On each iteration we traverse newly added filter's heritege tree
// 3) If currently indexed, existing filter is of type newly added filter, replace and continue
}
Of course the problem with the above logic, is that *all* filters whose class is within the added filter class inheritence tree would be replaced.
2) The other approach would be to develope intelligent filters (so no framework is required) each filter upon being added to the array of existing filters would need to know how to replace obsolete filters.
a. Existing filters know exactly which filters are derived and can replace them so they include a list/array of classnames
b. Child filters know which filters they can replace
I'm starting to loose myself though, I need to sleep
Cheers

Posted: Wed Feb 07, 2007 1:23 am
by feyd
Upon further thinking, you could have two more add methods that support overrides. One based on heritage, the other based in explicits.

.. Or it could be integrated into the add method of the Manager where you simply have multiple managers that could be used.
Posted: Wed Feb 07, 2007 2:42 pm
by Ambush Commander
Mmm....
1) Do you use an explicit approach, for example does the caller determine what filter get's replaced/overriden?
2) Do you use an automated/implicit approach. Does each filter have the intelligence to know which filters it is to disable (what arborint has suggested - I think?)?
I'm currently using an implicit approach. The module loading the filter, ideally, shouldn't know anything about the manager, the manager is the one who reads the module and figures the stuff out. However, the explicit approach may be more desirable.
Class heritage may be one path to automating the override status.. although that could produce poor behavior in some peoples' eyes.
Ooh, that's deep wizardry. But if a filter needs to disable two others, you're sunk. Probably not a good idea.
I think explicit is best for experienced programmers, but is that the audience?
Since this is more direct modifications into the code, I'd say experienced, but I want the learning curve as low as possible.
[snip]
Hmm... could I change the situation around a little bit? Here's what the code actually looks like:
Code: Select all
class Module
{
var $filters = array();
}
class Module_Wizardry extends Module
{
function Module_Wizardry() {
$this->filters[] = new Filter_DeepMagic();
}
}
class Schema
{
var $modules = array();
var $filters = array();
function Schema() {
$this->modules['Wizardry'] = new Module_Wizardry();
// more modules, dynamic and conditional loading, etc
}
function setup() {
foreach ($this->modules as $module) {
foreach ($module->filters as $filter) {
$this->filters[] = $filters;
}
}
}
}
And I'm lamenting how, with this setup, there's no way to disable Filter_DeepMagic if you want to override it with a new Filter_DeepBlueMagic.
A quick fix would be to use:
Code: Select all
class Module_Wizardry extends Module
{
// ...
function Module_Wizardry() {
$this->filters['DeepMagic'] = new Filter_DeepMagic();
}
}
class Module_SuperWizardry extends Module
{
// ...
function Module_SuperWizardry() {
$this->filters['DeepMagic'] = false; // remove filter
$this->filters['DeepBlueMagic'] = new Filter_DeepBlueMagic();
}
}
class Schema
{
// ...
function setup() {
foreach ($this->modules as $module) {
foreach ($module->filters as $filter_name => $filter) {
$this->filters[$filter_name] = $filters;
}
}
}
}
Integrating a manager would require a very major restructuring of the code:
Code: Select all
class Module_Wizardry extends Module
{
// ...
function loadFilters(&$manager) {
$manager->add(new Filter_DeepMagic());
}
}
class Schema
{
// ...
var $filterManager;
function setup() {
foreach ($this->modules as $module) {
$module->loadFilters($this->filterManager);
}
}
}
Like a Visitor pattern. You guys have run even further with it: the filter manager can implement all sorts of complicated overriding mechanisms:
1. Overriding based on inheritance (child class overrides parent)
2. Explicitly override another named filter (use a remove() function or something)
3. Query the filter object for information on inheritance (actually, one wouldn't need to pass in the manager to do that).
I'm wary about the third method because it's not very explicit. I'm wary about using the manager because it's very bulky.