Search code examples
perlclassconstructorshared

Perl: Using common constructor for base and subclass


I am trying to initialize a base class and a subclass without having to copy the constructor. This is what I got:

tstbase.pm:

package tstbase;
use Exporter qw(import);
our @EXPORT = qw(&new);
my %config = (
    "class" => "tstbase",
);

sub new {
    my $class = shift;
    my $self;
    $self->{"name"} = $config{"class"};
    bless ($self, $class);
    return $self;
};
1;

tstsubclass.pm:

package tstsubclass;
use tstbase;
my %config = (
  "class" => "tstsubclass",
);
1;

tst.pl:

#!/usr/bin/perl
use tstsubclass;

my $baseobj = tstbase->new;
print "Testbase ".$baseobj->{"name"}."\n";
my $subobj = tstsubclass->new;
print "Testsubclass ".$subobj->{"name"}."\n";

The outout of tst.pl is

Testbase tstbase
Testsubclass tstbase

but I am looking for

Testbase tstbase
Testsubclass tstsubclass

which I get when I copy the "sub new { .. }" routine over to tstsubclass.pm. Is there a way to avoid that overhead? I have tried all combinations of my %config / our %config and exporting %config with no success.

Any help is greatly appreciated

Best, Marcus


Solution

  • Your constructor is inherited, so that's working fine. What's not working is your use of %config, which exists separately in each package. Because you're calling the constructor defined in your base class, that version of %config is used. In your specific case, the config hash is unnecessary, since you can just initialize the name member by using the $class variable passed in to your constructor:

    sub new {
        my $class = shift;
        my $self = { };     # initialize the object as a reference to an empty hash
        $self->{"name"} = $class;
        bless ($self, $class);
        return $self;
    };
    

    This will work (although it's unnecessary; you can always get the class of an object using Scalar::Util::blessed).

    But the more general question appears to be about how to use class-specific configuration information in an inherited constructor. One way to do it would be to use a separate initialization step which can be overridden in the child class.

    package tstbase;
    
    # we don't use Exporter for OO code; exporting methods is highly counterproductive.
    # we should also turn on strict and warnings.
    use strict;
    use warnings;
    
    my %config = (
        "class" => "tstbase",
    );
    
    sub new {
        my $class = shift;
        my $self;
        bless $self, $class;
        $self->_init( %config );
        return $self;
    };
    
    sub _init { 
        my $self = shift;
        my %args = @_;
        $self->{name} = $args{class};
    }
    
    1;
    

    And then:

    package tstsubclass;
    use parent 'tstbase';   # we have to say what class we're extending
    
    my %config = (
      "class" => "tstsubclass",
    );
    
    sub _init { 
        my $self = shift;
        $self->SUPER::_init( %config );
    }
    
    1;
    

    In this case, your subclass's _init method will get called by the constructor in the parent class, which calls the parent class's _init method, but passing in its local %config.

    An easier way to handle this would be to use mixins, or Moose roles.