Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling base constructor in perl

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;
like image 838
Zitrax Avatar asked Mar 09 '11 17:03

Zitrax


3 Answers

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

like image 182
Ether Avatar answered Oct 16 '22 17:10

Ether


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.

like image 22
Ven'Tatsu Avatar answered Oct 16 '22 18:10

Ven'Tatsu


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

like image 21
ikegami Avatar answered Oct 16 '22 16:10

ikegami