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;
}
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.