http://qs321.pair.com?node_id=603009

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

I have plenty of procedural experience with Perl, but not that much OOP, and this is my first real foray into inheritance. I'm writing an abstract base class with two subroutines, a fully functional constructor, and an unimplemented method:
package MyClass; # fully functional constructor method sub new { # if we're trying to instantiate the abstract class, die my $class = shift; die "abstract class must be subclassed" if $class eq 'MyClass'; # do some processing, all with lexically scoped variables # ... # call (as of yet) unimplemented function to return a hashref my $self = _underthehood(); die "_underthehood() not implemented" if ref $self ne 'HASH'; # bless hashref as instance of the current package, and return it bless $self, $class; return $self; } # unimplemented member method sub _underthehood { return; } 1;
Ideally, someone could then subclass that base module, and just implement the _underthehood() method to return a hashref:
package MyClass::Implemented; use base 'MyClass'; # newly implemented member method sub _underthehood { # do some processing on the given arguments, #creating a populated hashref my $hashref = { 'key' => 'value' }; return $hashref; } 1;
Now, the subclass could be directly invoked, and all is well with the world:
#!/usr/bin/perl -w use strict; use MyClass::Implemented; my $obj = new MyClass::Implemented();
But, as usual, I'm doing something wrong, because even though new() is invoked and has a class value of 'MyClass::Implemented', the script dies because _underthehood() is returning undefined, which means its actually calling MyClass::_underthehood(). So how can I get it to call MyClass::Implemented::_underthehood() from within the generic base class' constructor?

__________
Systems development is like banging your head against a wall...
It's usually very painful, but if you're persistent, you'll get through it.

Replies are listed 'Best First'.
Re: Inheritance and calling a subclass method *from* a baseclass method...
by merlyn (Sage) on Mar 03, 2007 at 02:21 UTC
    You should be calling _underthehood as a class method, not as a subroutine. Then the right thing would happen:
    sub new { my $class = shift; ... my $self = $class->_underthehood(); ... } sub _underthehood { my $class = shift; die "subclass responsibility"; }
      Thanks Merlyn, that did it! Just one thing, since its being called as a method (with the indirection operator), it passes the classname as the initial parameter...There's no way around that, right?

      __________
      Systems development is like banging your head against a wall...
      It's usually very painful, but if you're persistent, you'll get through it.

        That's right; that's how you get the invocant in Perl.

        (As a side note, beware of the indirect constructor call; my $object = Some::Class->new() is much more reliable than my $object = new Some::Class(). The latter is very sensitive to order of declaration during parsing; best to avoid it where possible.)

      I would use Carp::confess in this case. And I probably wouldn't check if the method was overloaded on new(). Please consider the following example:

      package MyPackage; use strict; use warnings; use Carp qw(confess); sub new { my ($class) = @_; my $self = {}; bless $self, $class; return $self; } sub abstract_method { my ($self) = @_; my ($method) = ( caller(0) )[3]; confess "${method} should be overloaded."; } package main; my $p = MyPackage->new(); $p->abstract_method();

      Don't you think it is cleaner?

      Igor 'izut' Sutton
      your code, your rules.

        I think the two primary pieces of information I would want in a "method should be overloaded" error message are the name of the method and the name of the class that didn't implement it. So I really think you need to report the latter (my $class= ref $self || $self;).

        I'm not sure why you are writing code to look up the string "abstract_method" (perhaps so you can re-use this subroutine similar to *replace_me= \&abstrace_method;? -- no, doing so would still report "abstract_method" according to my testing, unfortunately). And, at least in this case, you don't even need to include the method name in the text since Carp::confess will already show that in the stack trace (though the error message is clearer if you do, of course). :)

        - tye