What is the correct way to call the base constructor from the class constructor in Perl ?
I have seen syntax like this:
my $class = shift;
my $a = shift;
my $b = shift;
my $self = $class->SUPER::new($a, $b);
return $self;
Is this correct ? What if we have several parent classes. For example a class like this:
package Gamma;
use base Alpha;
use base Beta;
sub new
{
# Call base constructors...
}
1;
This problem is why some people recommend not doing anything interesting in your new
method.
new
is expected to create and return a blessed reference, it's hard to make a system that handle doing this twice for the same object from different parent classes.
A cleaner option is to have a new method that only creates the object and calls another method that can set up the object. This second method can behave in a way that allows multiple parent methods to be called. Effectively new
is you allocator and this other method is your constructor.
package Mother;
use strict;
use warnings;
sub new {
my ($class, @args) = @_;
my $self = bless {}, $class;
return $self->_init(@args);
}
sub _init {
my ($self, @args) = @_;
# do something
return $self;
}
package Father;
use strict;
use warnings;
sub new {
my ($class, @args) = @_;
my $self = bless {}, $class;
return $self->_init(@args);
}
sub _init {
my ($self, @args) = @_;
# do something else
return $self;
}
package Child;
use strict;
use warnings;
use base qw(Mother Father);
sub _init {
my ($self, @args) = @_;
# do any thing that needs to be done before calling base classes
$self->Mother::_init(@args); # Call Mother::_init explicitly, SUPER::_init would also call Mother::_init
$self->Father::_init(@args); # Call Father::_init explicitly, SUPER::_init would NOT call Father::_init
# do any thing that needs to be done after calling base classes
return $self;
}
Ether is right about the complications your likely to find by using multiple inheritance. You still need to know that Father::_init
won't override any decisions made by Mother::_init
to start with. It will only get more complicated and harder to debug from there.
I would second the recommendation of Moose, its interface includes a better separation of the object creation and initialization than my example above that usually just works.
If all your constructor is doing is calling the parent constructor (as in your example, you don't need to write one at all. Simply leave it out and the parent will be called; you just need to ensure that the object is blessed into the right type:
package Parent;
use strict;
use warnings;
sub new
{
my ($class, @args) = @_;
# do something with @args
return bless {}, $class;
}
1;
If you use the above code and have a Child
class declared with use parent 'Parent';
then the Parent constructor will properly construct a child.
If you need to add some properties in the Child, then what you had is largely correct:
package Child;
use strict;
use warnings;
use parent 'Parent';
sub new
{
my ($class, @args) = @_;
# possibly call Parent->new(@args) first
my $self = $class->SUPER::new(@args);
# do something else with @args
# no need to rebless $self, if the Parent already blessed properly
return $self;
}
1;
However, when you bring multiple inheritance into the mix, you need to decide the right thing to do at every step of the way. This means a custom constructor for every class that decides how to merge the properties of Parent1 and Parent2 into the child, and then finally blesses the resulting object into the Child class. This complication is one of many reasons why multiple inheritance is a bad design choice. Have you considered redesigning your object heirarchy, possibly by moving some properties into roles? Further, you might want to employ an object framework to take out some of the busy work, such as Moose. Nowadays it is rarely necessary to write a custom constructor.
(Lastly, you should avoid using the variables $a
and $b
; they are treated differently in Perl as they are the variables used in sort functions and some other built-ins.)
When using multiple inheritance, the default method resolution order is sub-par. I strongly recommend that you add
use mro 'c3';
to the modules and that you call the next constructor in the chain using
sub new {
my ($class) = @_;
return $class->next::method(@_);
}
Alpha and Beta would have to do the same for this to work.
Ref: mro