views:

73

answers:

2

Possible Duplicate:
How to have Moose return a child class instance instead of its own class, for polymorphism

Suppose I have two related classes MyClass::A and MyClass::B that are both subclasses of MyClass. I would like the constructor for MyClass to take a filename, read the file, and based on the contents of the file, decide whether the file is of type A or B. Then, it should construct an object of the appropriate subclass and return it. Edit: Actually, it should call the constructor of the appropriate subclass and return the result.

So for example, after this code,

my $filename = "file_of_type_A.txt";
my $object = MyClass->new($filename);

$object should be an instance of MyClass::A. This seems like valid behavior, because $object->isa('MyClass') will always return true. I was thinking of using introspection to get a list of all subclasses of MyClass, and then trying to construct each in turn, until one succeeds. However, I don't see a way to modify the Moose constructor to do this. Neither the BUILDARGS nor BUILD hooks seem appropriate.

So how can I modify a Moose class's constructor to select an appropriate subclass and delegate construction to that subclass?

A: 

An instance of a class is just a reference that is blessed to that class. To make an object into an instance of another class, you just need to bless it to that other class.

package PackageOne;

sub new {
    my (@constructor_args) = @_;
    my $self = { key1 => value1, key2 => value2, ... };
    ...
    if (i_decide_I_want_to_return_PackageTwo()) {
        return bless $self, 'PackageTwo';
    } else {
        return bless $self, __PACKAGE__;
    }
}
mobrule
I'm using Moose, not the regular Perl OO. Moose defines its own constructor, which you're not supposed to override. It provides hooks to do things before or after construction, but no obvious way to affect the construction process itself. Also, I need to call the subclass's constructor.
Ryan Thompson
+5  A: 

As Ether said above, you should have a factory method do this task. I like to put the factory method in a separate package/class from the things it produces Usually this is a virtual class that has no attributes or constructor, but if you want to do things like keep track of what you have made, you can create an actual factory object.

package MyFile::Factory;

sub make_file_object {
    my $class = shift;
    my $filepath = shift;

    my $type = $class->determine_file_type( $filepath );
    my $file_class = $class->get_class_for_file_type( $type );

   return $file_class->new( $filepath, @_ );
}

# implement those other methods down here.

1;
daotoad
Then I would use the factory like this? `my $object = MyFile::Factory->make_file_object($filepath)`
Ryan Thompson
Also, should the common superclass now become a role instead? The only reason I wanted it to be a class in the first place was to give it a constructor, and if I'm using a factory instead, then there's no need for that.
Ryan Thompson
Your factory class isn't part of the MyFile inheritance hierarchy. It sits alongside and acts on the hierarchy.
daotoad