Search code examples
perlinheritanceconstructordesign-patternscreation

How can I create an object whose derived class is specified implicitly by the creation properties?


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

Solution

  • 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.