Mechanisms to override behavior

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Mechanisms to override behavior

Post 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.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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.
(#10850)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post 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?
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Arggghh neeeed sleep.


bye
wei
Forum Contributor
Posts: 140
Joined: Wed Jul 12, 2006 12:18 am

Post 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...
       }
}
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
wei
Forum Contributor
Posts: 140
Joined: Wed Jul 12, 2006 12:18 am

Post 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.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Perhaps. It's a bit bulky, but it may work out nicely. Abstracting the manager certainly is a very good possibility.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post 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 :)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post 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?
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post 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 :)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

Upon further thinking, you could have two more add methods that support overrides. One based on heritage, the other based in explicits. :idea: .. Or it could be integrated into the add method of the Manager where you simply have multiple managers that could be used.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
Post Reply