jaa has asked for the wisdom of the Perl Monks concerning the following question:

I have looked in object oriented section Q&A for something like this, but didn't quite find the slant I am looking for.

When I create a child class, how should I ensure that the parent class constructor is properly called?

I am currently doing something like this - is there a better way?

use strict; # This should do ALL the _initXXX()s # even though myGrandChild does not know # about _initParent() or _initChild()! my $it = myGrandChild->new('a','b','c'); #---------------------------------- package myParent; our @ISA; sub new { my $class = shift; my $self = $ISA[0] ? $ISA[0]->new(@_) : {}; bless $self,$class; $self->_initParent(@_); return $self; } sub _initParent { my $self = shift; print "_initParent @_ [ISA: @ISA]\n"; } #---------------------------------- package myChild; our @ISA; use base qw(myParent); sub new { my $class = shift; my $self = $ISA[0] ? $ISA[0]->new(@_) : {}; bless $self,$class; $self->_initChild(@_); return $self; } sub _initChild { my $self = shift; print "_initChild @_ [ISA: @ISA]\n"; } #---------------------------------- package myGrandChild; our @ISA; use base qw(myChild); sub new { my $class = shift; my $self = $ISA[0] ? $ISA[0]->new(@_) : {}; bless $self,$class; $self->_initGrandChild(@_); return $self; } sub _initGrandChild { my $self = shift; print "_initGrandChild @_ [ISA: @ISA]\n"; } #----------------------------------

Obviously calling _initXXX()s is a facet of this being a trivial example - a number of classes in our library do more complex things when created. Also, they are not all in-house, so enforcing an additional naming standard - i.e. assuming that there is a $self->SUPER::_init() does not work.

Regards,

Jeff

Replies are listed 'Best First'.
Re: maintaining constructor inheritance cascade
by dragonchild (Archbishop) on May 14, 2003 at 14:12 UTC
    I'm a little confused. Why aren't you just doing:
    package myParent; our @ISA; sub new { my $class = shift; bless $self,$class; $self->_init(@_); return $self; } sub _init { my $self = shift; print "_initParent (@_)\n"; } #---------------------------------- package myChild; our @ISA; use base qw(myParent); # This function is completely unnecessary #sub new { # my $class = shift; # my $self = $class->SUPER::new; # $self->_init(@_); # return $self; #} sub _init { my $self = shift; print "_initChild (@_)\n"; }
    Or, am I missing something?

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      The _initXXX() subs are all private subs that need to ALL be called in a cascade when an instance is created. This is the output from my example:
      _initParent a b c [ISA: ] _initChild a b c [ISA: myParent] _initGrandChild a b c [ISA: myChild]
      And this is the output from your mod:
      _initChild (a b c) _initGrandChild a b c [ISA: myChild]
      The myParent->_init() code never got executed. Yes, you could call $self->SUPER::_init(@_) in your myChild->_init(), but that is missing the point.

      The _initXXX() methods are private to the respective classes - they are not part of the external interface, and are not intended to be specialised by a child class.

      Hope that makes sense?

        Ok. I get the picture now. (Sounds like a goofy design to me, but whatever.)
        package myParent; our @ISA; sub new { my $class = shift; bless $self,$class; $self->_parent_init(@_); return $self; } sub _parent_init { my $self = shift; print "_initParent (@_)\n"; } #---------------------------------- package myChild; our @ISA; use base qw(myParent); sub new { my $class = shift; my $self = $class->SUPER::new; $self->_child_init(@_); return $self; } sub _child_init { my $self = shift; print "_initChild (@_)\n"; }
        Etc. The idea is that your constructor is a wrapper on your parent's constructor, but you do additional things after that's called. So, the delta off your original code is that there is only one bless - in the ultimate parent's code. (You were doing a bunch of reblessing, which I think was confusing you.)

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: maintaining constructor inheritance cascade
by jaa (Friar) on May 14, 2003 at 13:37 UTC
    mmmm - and what about multiple-inheritance constructor cascades?
Re: maintaining constructor inheritance cascade
by Anonymous Monk on May 14, 2003 at 15:31 UTC
    Why would you wanna do that? I just don't see any point in doing this -- can you explain more?

      mmmm... dragonchild also thought this was a goofy design and yet I always do this - a habit tatooed into me when I was a mere Perl love-child.

      So I went and checked with some venerated Object Ogres, UML Gods, demi-gods, and Snippet Slayers...

      the concerted response was that as a generalisation, an inherited constructor should ALWAYS ensure that the parent constructor has been executed before doing any localised specialisation.

      So I have cast around for a trivial example that shows why you MUST call the parent class constructor...

      This is the output from the code below - $enterprise works, but $voyager blows up!

      Starting Enterprise... StarShip initialised... Enterprise starship initialised... Enterprise crew count: 0 Starting Voyager... Voyager starship initialised... Can't use an undefined value as an ARRAY reference at ./trek.pl line 39.

      The difference is that Enterprise correctly calls the StarShip constructor, whereas Voyager decides to do its own thing, not realising that they are saving up a warp breach!

      #!/usr/local/bin/perl -w use strict; # This example shows why child class should always # execute their parent constructor: # - Enterprise class gets it right # - Voyager class blows up print "Starting Enterprise...\n"; my $enterprise = Enterprise->new( captain => 'pickard', compliment => 3000 ); print "Enterprise crew count: ", $enterprise->getCrewCount(),"\n\n"; print "Starting Voyager...\n"; my $voyager = Voyager->new( captain => 'janeway', compliment => 300 ); print "Voyager crew count: ", $voyager->getCrewCount(),"\n\n"; #--------------------------------------------------- package StarShip; sub new { my $class = shift; my $self = {}; bless $self,$class; $self->_ssInit(@_); return $self; } sub _ssInit { my $self = shift; my %param = @_; for my $property qw( captain compliment ) { die "StarShip: missing '$property'" unless $param{$property}; $self->{$property} = $param{$property}; } $self->{crew} = []; print "StarShip initialised...\n"; } sub getCrewCount { my $self = shift; return scalar @{$self->{crew}}; } #--------------------------------------------------- package Enterprise; use base qw(StarShip); sub new { my $class = shift; # We perform our parents constructor # to ensure that our object is in a consistent # state from the parents perspective my $self = $class->SUPER::new(@_); $self->_entInit(@_); return $self; } sub _entInit { my $self = shift; $self->{class} = 'constellation'; $self->{id} = 'NCC1701D'; print "Enterprise starship initialised...\n"; } #--------------------------------------------------- package Voyager; use base qw(StarShip); sub new { my $class = shift; # Whoops - we didnt call StarShip constructor! my $self = {}; bless $self,$class; $self->_voyagerInit(@_); return $self; } sub _voyagerInit { my $self = shift; $self->{class} = 'smaller'; $self->{id} = 'VOYAGER1'; $self->jumpTo('gamma quadrant'); print "Voyager starship initialised...\n"; } sub jumpTo { my $self = shift; $self->{x} = rand(-1)*1000; $self->{y} = rand(-1)*1000; $self->{z} = rand(-1)*1000; }