Chris
Chris

Reputation: 354

Proper Perl OO Inheritance

I'm trying to create a child class of a custom module that we have in our code base. We have our modules in a directory that we include in all of our files. So we begin with

use Env;
use lib "$ENV{OurKey}/RootLib"; # All of our modules are here

Next, I have my parent module, located in RootLib/Dir1/Parent.pm whose code has been long standing, and so I'd rather not change any of it, but rather have the child be able to inherit from it as it is.

package Parent;
use strict;
use warnings;
use Env;
use lib "$ENV{OurKey}/RootLib";

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = {};

    # Some other stuff

    bless ($self, $class);
    return $self;
}

At this point, I'm a bit lost, because I've seen a number of different ways to define the child constructor, but none have worked for me. This is what I have, but it does not work because subroutines that should be inheritied from the parent cannot be found in the child package. The child package is in RootLib/Dir1/Dir2/Child.pm

package Child;
use strict;
use warnings;
use vars qw(@ISA);
use Env;
use lib "$ENV{OurKey}/RootLib";

require Dir1::Parent;
push @ISA, 'Dir1::Parent';

sub new {
    # This constructor is clearly incorrect, please help
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = Parent::new($class);

    bless ($self, $class);
    return $self;
}

And then, in my test.pl file, I have

use Env;
use lib "$ENV{OurKey}/RootLib";
use Dir1::Dir2::Child;

my $childObj = Child->new();
$childObj->inheritedParentSubroutine( ... ); # Cannot find this subroutine

Upvotes: 2

Views: 203

Answers (2)

ikegami
ikegami

Reputation: 385645

The to-be parent class has package Parent;, but then you have @ISA = 'Dir1::Parent';. Those don't match, which is why the method can't be found. (That, and the fact that you didn't actually define the method anywhere!)

The file name, the name in the package statement, the name in the use statement, and the name in @ISA must all match.

What follows are two possible solutions.


If the classes are named Dir1::Parent and Dir1::Dir2::Child

use lib "$ENV{OurKey}/RootLib";

use Dir1::Parent      qw( );
use Dir1::Dir2::Child qw( );

(Note that use Env was removed since it wasn't being used.)

(.../RootLib/)Dir1/Parent.pm:

package Dir1::Parent;          # Must match the file name.
use strict;
use warnings;

...

(.../RootLib/)Dir1/Dir2/Child.pm:

package Dir1::Dir2::Child;     # Must match the file name.
use strict;
use warnings;

use Dir1::Parent qw( );        # Must match the file name
our @ISA = 'Dir1::Parent';     #   and the package name.
   -or-
use parent 'Dir1::Parent';     # This can replace the other two lines.

...

If the classes are named Parent and Child

Don't modify @INC (e.g. via use lib) in modules. Your script should contain the following:

use lib
   "$ENV{OurKey}/RootLib/Dir1",
   "$ENV{OurKey}/RootLib/Dir1/Dir2";

use Parent qw( );
use Child  qw( );

(Note that it's really weird to have a library directory inside of another library directory!)

(.../RootLib/Dir1/)Parent.pm:

package Parent;                # Must match the file name.
use strict;
use warnings;

...

(.../RootLib/Dir1/Dir2/)Child.pm:

package Child;                 # Must match the file name.
use strict;
use warnings;

use Parent qw( );              # Must match the file name
our @ISA = 'Parent';           #    and the package name.
   -or-
use parent 'Parent';           # This can replace the other two lines.

...

As for the constructors,

Parent:

sub new {
    my $class = shift;                # That proto thing is a bad practice.
    my $self = bless({}, $class);     # Don't need two lines to do this.

    # ...

    return $self;
}

Child:

sub new {
    my $class = shift;                # That proto thing is a bad practice.
    my $self = $class::SUPER->new();  # Don't need two lines to do this.
                                      # If you don't need to do anything here,
    # ...                             #   you can just inherit the parent's new
                                      #   by removing this sub entirely.
    return $self;
}

Upvotes: 0

mob
mob

Reputation: 118595

The parent class declares it is in package Parent (even if the file that contains it is Dir1/Parent.pm), so the child's @ISA should just include Parent.

Or you can work around your unfortunate directory structure with some additional use lib ... statements.

package Child;
use lib "$ENV{OurKey}/RootLib/Dir1";
require Parent;      # found in $OurKey/RootLib/Dir1/Parent.pm
our @ISA = ('Parent');
...

# test.pl
use lib "$ENV{OurKey}/RootLib/Dir1/Dir2";
use Child;           # found in $OurKey/RootLib/Dir1/Dir2/Child.pm
...

Upvotes: 2

Related Questions