views:

146

answers:

4

I'm looking for a pattern for the following. (I'm working in Perl, but I don't think the language matters particularly).

With a parent class Foo, and children Bar, Baz, Bazza.

One of the methods for constructing a Foo is by parsing a string, and part of that string will implicitly specify which class is to be created. So for example if it starts 'http:' then it's a Bar, but if it doesn't but it contains '[Date]' then Baz likes it, and so on.

Now, if Foo knows about all its children, and what string is a Bar, what is a Baz etc, it can call the appropriate constructor. But a base class should not have any knowledge about its children.

What I want is for Foo's constructor to be able to try its children in turn, until one of them says "Yes, this is mine, I'll create the thing".

I realise that in the general case this problem is not well-defined, as there may be more than one child which will accept the string, and so the order in which they are called matters: ignore this and assume that the characteristics of the string are such that only one child class will like the string.

The best I have come up with is for the child classes to 'register' with the base class on initialisation, so that it gets a list of constructors, and then loop through them. But is there a better method that I'm missing?

Sample code:

package Foo;

my @children;

sub _registerChild
{
  push @children, shift();
}

sub newFromString
{
  my $string = shift;
  foreach (@children) {
    my $object = $_->newFromString(@_) and return $object;
  }
  return undef;
}

package Bar;
our @ISA = ('Foo');

Foo::_registerChild(__PACKAGE__);

sub newFromString
{
  my $string = shift;
  if ($string =~ /^http:/i) {
    return bless(...);
  }
  return undef;
}
A: 

You may implement a arbitrary lookup algorithm in class Foo, that searches for existing child classes. Maybe based on config files provided with child classes, or with any other mechanism you might think of.

The class Foo will then detect the existing client classes at runtime and call them in turn.

Additionaly you may cache the lookup results and come close to the registry solution you already described yourself.

mkoeller
Thanks. It seems to me that if I've got to do something like that then my 'register' is simpler and clearer. I was hoping there was an established pattern that I'd missed.
Colin Fine
The decision comes down to the use case you'd like to address. If you want to keep you Foo factory open to yet unknown 3rd-party child classes, then you'll do a lookup.If you live in a controlled environment, supporting only you own code, then the registry'll be fine.
mkoeller
+5  A: 

Perhaps you could implement this with Module::Pluggable? This would remove the need for registration.

The approach I've taken before was to use Module::Pluggable to load my child modules (this allowed me to add new child modules by simply writing and installing them). Each of the child classes would have a constructor that either returned a blessed object or undef. You loop over your plugins until you get an object, then return it.

Something like:

package MyClass;
use Module::Pluggable;

sub new
{
    my ($class, @args) = @_;
    for my $plugin ($class->plugins)
    {
       my $object = $plugin->new(@args);
       return $object if $object;
    }
}

There's Class:Factory as well but that may be a little over the top for your needs.

Nic Gibson
Thanks. Logically Module::Pluggable is pretty well exactly what I was looking for; but it might not be ideal for our situation because it probably won't be present on our users' machines, so we'd have to distribute it with our own modules. It's also 'magic' compared to my registration solution, so it'll probably be less clear. But thanks for bringing it to my attention.
Colin Fine
It is sort of magic but the default behaviour would load classes in the MyClass::Plugin namespace only (and that namespace is easy to override). So, registration would be effectively identical to creating a module in that namespace. It is, obviously, a CPAN module but I would be surprised if you could manage any sort of serious application in Perl these days without CPAN (or a great deal of wheel reinvention).
Nic Gibson
A: 

If you take your comment about the parent class not containing info about the chilidren and your method of delegating the task of establishing a child classes suitability to the class itself then it is probably correct to factor out the class selection from the parent class and create a singleton for this task.

at least that would be my preference ... from this your current parent class (which presumably has some common functionality across your child classes) can presumably then either become abstract or an interface.

the singleton could then manage the construction of all the child classes and their distribution (clone them if they are not functional?) ... moreover the child classes can be moved into a separate dll to promote separation.

sorry that is not a direct solution. I have done this in the past by managing a list of classes in the singleton much like you are here. the idea behind the singleton is that if you do want to use any expensive reflection you only have to do it once.

John Nicholas
Thanks - it's not a singleton, it's an abstract base class. Once the objects are created they'll get the right methods by polymorphism, it's how to handle the case when there's not yet a subclassed object that i was worried about.
Colin Fine
+1  A: 

It seems you're trying to have a single class be both a base class and a factory. Don't. Use 2 separate classes. Something like this:

package Foo;

package Bar;
use base 'Foo';

package Baz;
use base 'Foo';

package Bazza;
use base 'Foo';

package Factory;
use Bar;
use Baz;
use Bazza;

sub get_foo {
    my ($class, $string) = @_;
    return Bar->try($string) || Baz->try($string) || Bazza->try($string);
}

And then use it like:

my $foo = Factory->get_foo($string);

This way your base class doesn't need to know about your child classes, only your factory does. And child classes don't need to know about each other either, only Factory needs to know the details of which child classes to try and in which order.

mpeters
With this approach you even have a place in code that needs to know about the complete set of child classes. I think that's even worse than the registry solution. If you want to add/remove a child class, you'll have to take that some code somewhere else is modified consistently. So your idea does not add flexibility against change, as patterns are usually aimed to.
mkoeller
@mkoeller, it's trivial to make the factory automatically load up a set of classes in a particular namespace ala Module::Pluggable. I generally prefer to be explicit about these kinds of things to avoid surprises, but that's more a taste issue rather than flexibility against change. Flexibility means it's it would be easy to add new sub-classes that can process this string. It doesn't need to mean that it handles them auto-magically if they are present on the file system.
mpeters