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

So after reading perltoot, I decided to have a go at creating some oo-perl code. I had a project in mind and started designing the various objects I would use and grouping them into various modules. I eventually decided that I wanted my constructor to be inherited by a subclass from its superclass. But I've run into trouble there. I'm using a slightly modified form of the AUTOLOAD for data accessors example from perltoot. In my first module Some/Package.pm I have:
package Some::Package; use strict; use warnings; our $VERSION = '0.1'; our $AUTOLOAD; my %fields = ( name => undef, ); sub new { my $proto = shift; my $class = ref($proto) || $proto; my %args = @_; my $self = { _permitted => \%fields, %fields, }; bless($self, $class); foreach my $arg (keys(%args)) { $self->$arg($args{$arg}); } sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; unless(exists $self->{_permitted}->{$name} ) { croak "Can't access `$name' field in class $type"; } if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } 1;
In the module that defines the subclass, Some/Package/Sub.pm, I have the following:
package Some::Package::Sub; use strict; use warnings; our $VERSION = '0.1'; our $AUTOLOAD; my %fields = ( name => undef, needthis => undef, ); 1;
Finally, my test script looks something like this:
use strict; use warnings; use Some::Package; use Some::Package::Sub; my $object = Some::Package::Sub->new( name => 'Test', needthis => 'Value', }
Now, it seems that Some::Package::Sub inherits the constructor and AUTOLOAD methds just fine, but that the inherited constructor, which makes use of a reference to the file scoped lexical %fields, refers to the hash in Some/Package.pm even though I'm calling Some::Package::Sub->new(). This means that the data accessor for the needthis attribute is not available in Some::Package::Sub objects. How do I solve this?

Replies are listed 'Best First'.
Re: Inheritence of a Constructor that uses a file-scoped lexical?
by mce (Curate) on Oct 29, 2002 at 13:51 UTC
    Hi,

    First of all, please avoid the word fields as it is very confusing in OO. (pseudo hashes, etc...) as they are not lexically scoped (don't use my with fields).

    Second, I see no inheritance operator in your classSome::Package::Sub. It misses the line

    use base qw(Some::Package);
    Third: The line my %fields is totally useless in the package Some::Package::Sub, as it is lexically scoped, and only consultable from with the package itself. I would suggest to create an init subroutine, and call this from inside the new statement. f.e.
    sub new { my $proto = shift; my $class = ref($proto) || $proto; my %args = @_; bless($self, $class); $self->init(); foreach my $arg (keys(%args)) { $self->$arg($args{$arg}); } }
    And in the Some::Package::Sub
    sub init { my $self=shift; $self->{_permitted} = \%fields; #... and add the other fields also };
    It least, this is the way I would do it, but since I am not perfect....
    ---------------------------
    Dr. Mark Ceulemans
    Senior Consultant
    IT Masters, Belgium

      Well, that's all well and good, since it does solve the problem of making sure that the reference to %fields points to the one in the appropriate file, but it sort of recreates the original problem I was trying to solve.

      The whole point of inheriting the constructor was that I had subclasses that were all using esentially the same code for a constructor, so why duplucate the code. If I made changes, I'd have to update each package, etc. Instead, inherit it. But that introduced the problem that started this thread.

      Your solution creates an init method, which must now exist in the same file as the subclass, and thus much exist in the same file for each and every subclass. While it significantly reduces the amount of code that is reproduced in each module, it doesn't eliminate it entirely.

      If I make init inheritable from the base clase though, then I run into the same problem again. The reference points to the hash in the wrong file. It seems to be a chicken and the egg problem. Perhaps file-scoped lexical isn't the right way to go, but then how else do I get the fields (a name I shall change now, thank you for the education) for each class set up if not through a file-scoped lexical?

Re: Inheritence of a Constructor that uses a file-scoped lexical?
by broquaint (Abbot) on Oct 29, 2002 at 14:42 UTC
    You could add a attribute initialization method in the parent package and have the Some::Package::Sub constructor call it along with the parent constructor e.g
    ## in Some::Package sub _init_attribs { %$fields = %{+pop}; } ## in Some::Package::Sub my %fields = map { $_ => 1 } qw( field_a anotherone ); sub new { my $self = shift; $self->_init_attibs(\%fields); $self->SUPER::new(@_); }
    So you can still use the %fields lexical and Some::Package's constructor. How's that?
    HTH

    _________
    broquaint

    update: changed code to DWOrsmoMeant

Re: Inheritence of a Constructor that uses a file-scoped lexical?
by valdez (Monsignor) on Oct 29, 2002 at 13:44 UTC

    I think you need use base 'Some::Package' in your sub class...

    HTH, Valerio

Re: Inheritence of a Constructor that uses a file-scoped lexical?
by robartes (Priest) on Oct 29, 2002 at 13:48 UTC
    Just a shot in the dark (I'm an OO newbie as well), but could you not specify your %fields reference with the class in question? In your Some::Package::new method:
    my $self = { _permitted => \%{$class::fields}, %{$class::fields}, };

    If you then call Some::Package::Sub->new, it uses Some::Package::Sub::%fields, which I think is what you want.

    Caveat: I have not tested the above code so, although the syntax checks OK, it might not work at all. But even then, it should give you an idea on how to proceed.

    CU
    Robartes-

      %fields was defined as a lexical my variable, so you cannot access it from the symbol table (ie, %{"${class}::fields"}). Define the hash as an our global variable (or use vars qw/%fields/; globals) in both classes and you can do it. This uses a taste of polymorphism (not really true polymorphism) in the new constructor -- no matter what inherited class it was called from, it uses that class's %fields hash.

      If you don't like bodging in the symbol table (won't work under strict), it might be smoother would be to have a fields accessor sub in each class that would simply return the %fields hash:

      package SomePackage; my %fields = ( name => undef ); ## can be a lexical ### here's a new simple accessor sub fields { return %fields }; sub new { my $proto = shift; my $class = ref($proto) || $proto; my %args = @_; ### added this line my %fields = $class->fields; my $self = { _permitted => \%fields, %fields, }; bless($self, $class); foreach my $arg (keys(%args)) { $self->$arg($args{$arg}); } } ## ... elsewhere package Some::Package::Sub; use base 'Some::Package'; ### you needed this line before my %fields = ( name => undef, required => undef ); sub fields { return %fields };
      This uses true polymorphism by calling $class->fields. You can keep the %fields hash as a lexical within the classes as well.

      blokhead

Re: Inheritence of a Constructor that uses a file-scoped lexical?
by jdporter (Paladin) on Oct 29, 2002 at 17:05 UTC
    In this slight modification to your code, the amount of code duplication is kept to an absolute minimum. All there is in each derived class is a function encapsulating the specific field set definition for that class. The constructor, which is defined only in the base class, calls the field subroutine in the actual class.

    This is an example of the Strategy pattern.

    package Some::Package; sub new { my $proto = shift; my $class = ref($proto) || $proto; my %args = @_; my $fields_hr = $class->fields; my $self = bless { _permitted => $fields_hr, %$fields_hr, }, $class; foreach my $arg ( keys %args ) { $self->$arg( $args{$arg} ); } $self } sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; exists $self->{_permitted}->{$name} or croak "Can't access `$name' field in class $type"; @_ and $self->{$name} = shift; $self->{$name} } # the base class also defines some field(s). sub fields { return { name => undef } } # # A derived class need only define the fields, and any other # class-specific data/behavior. # package Some::Package::Sub; use base qw(Some::Package); sub fields { return { name => undef, needthis => undef, } }
Re: Inheritence of a Constructor that uses a file-scoped lexical?
by Orsmo (Beadle) on Oct 29, 2002 at 13:51 UTC
    Minor correction: The Sub.pm does have the following line that I failed to mention. *blush*.
    our @isa = qw(Some::Package);

      Make that:

      our @ISA = qw(Some::Package);

      ...and it might work the way you expect it to. Perl is case-sensitive. :)

        ...must learn to check for typos before pressing submit... In the actual code, @ISA is correctly capitalized. Maybe next time, I'll just cut and paste the actual code and let the distracting extra cruft be damned.