February 10, 2005

Using a lot lately: Factory + implementation autodiscovery

If you’re not using IoC and you want decoupled code while still figuring out at runtime how to perform a particular action you’ve probably got a number of factories in your application. But how to let your factory know about all the implementations it can create?

Using Class::Factory you can add classes to the factory dynamically like this:

# declare the factory:
package My::Factory:
use base qw( Class::Factory );
 
# and add classes to it from elsewhere:
My::Factory->register_factory_type( hat   => 'My::Factory::Hat' );
My::Factory->register_factory_type( ascot => 'My::Factory::Ascot' );

And using Module::Find you can find all the classes to add to the factory at runtime with something like this:

use Module::Find;
useall( 'My::Factory' );

(NB: I haven't used M::F but have my own implementation of something that appears to do exactly the same thing.)

So why not have all the subclasses register themselves with the factory?

package My::Factory::Hat;
use base qw( My::Factory );
My::Factory->register_factory_class( hat => __PACKAGE__ );

And then have the factory find all its implementations?

package My::Factory:
use base qw( Class::Factory );
use Module::Find;
 
useall( __PACKAGE__ );

Those two simple steps create a system where a user can just drop a class in the right place and have it found by your framework. This is particularly useful if your set of objects gets collected and run together (using some additional metadata for ordering if necessary) as in my discussion of OpenInteract2::Setup a few weeks ago. There, a user can just add a class with the right name and dependencies (the metadata) and when the website starts up it gets found, imported, and executed in the right order.

Or in another example that I'm not quite done with: how do we associate an incoming URL (or some other identifier) with the action object responsible for generating its content?

Currently this is touched in a couple of places, always a bad smell. The request is responsible for parsing the URL into action and task names and then the controller (underdocumented in last release) uses this name to create the action. But the request doesn't know anything about actions -- it got put there just because we were already parsing URLs and it seemed appropriate to extract a little more data there.

So why not let a separate object figure this out? This whole thing got started because I wondered how hard it would be to create userdirs like the CPAN urls in the last paragraph -- that could just be one of the objects in a chain of responsibility that looks at the URL (or whatever else it wants) and decides what action to create.

Like I said, I'm not done with it yet. But the factory plus autodiscovery pattern makes this a snap to implement and test out. Even better, it will be extremely easy for other people using OpenInteract2 to add their own behaviors that are indistinguishable from the internal ones to perform this core job.

Next: Segue? Who needs a segue?
Previous: Useful but probably common XPath function