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

I have a project I'm working on that has plug in style modules and I'm unhappy with the way I'm extending my base class. Was wondering if I could get an opinion of a more standard way to do this that doesn't feel like I'm doing it wrong. I threw in a couple of curve balls into the mix to show that I'm changing things on each package from the base class, and exporting methods also. Sorry for its length, but needed to make it long to get the full example together:
#!/usr/bin/perl package TOP::TEST; sub new { my $class = shift; my $self = {@_}; $self->{'thing'}->{'var_1'} = 'this changed'; $self->{'thing'}->{'var_3'} = 'other thing'; bless $self, $class; return $self; } package TOP::TEST::EXTENDONE; sub new { my $class = shift; my $self = {@_}; $self->{'thing'}->{'var_1'} = 'this changed'; $self->{'thing'}->{'var_3'} = 'other thing'; bless $self, $class; return $self; } sub print_one { my ($self) = @_; return 'FROM ONE: '.$self->{'thing'}->{'var_1'}; } package TOP::TEST::EXTENDTWO; sub new { my $class = shift; my $self = {@_}; $self->{'thing'}->{'var_3'} = 'other thing changed'; bless $self, $class; return $self; } package main; #### For this example added them to this file #use TOP::TEST; #use TOP::TEST::EXTENDONE; #use TOP::TEST::EXTENDTWO; # at any point there might be an EXTENDTHREE, or EXTENDFOUR when neede +d # # this how it works now. But feels wrong for some reason my $thing = new TOP::TEST( var_1 => 'this', var_2 => 'that'); my $one = new TOP::TEST::EXTENDONE( thing=> $thing,something=> 'extra' + ); my $two = new TOP::TEST::EXTENDTWO( thing=> $thing ); # this would be ideal with automatic inheritence in the packages... bu +t # can seem to get it done correctly. # Need some wisdom :( #my $thing = new TOP::TEST( var_1 => 'this', var_2 => 'that', somethin +g=>'extra'); print 'var_1 changed: '.$thing->{'var_1'}."\n"; print 'var_2: '.$thing->{'var_2'}."\n"; print 'var_3: '.$thing->{'var_3'}."\n"; print 'var_1 from $one: '.$one->print_one()."\n";
At any point I want to be able to write another EXTENDMETHOD and have it alter just "work". Because of my environment requirements I can't use MOOSE, but everything is OOP. Having everything just magically slide together would be ideal, am I missing a base class in between or something - or something else? The ideal scenerio being:
use TOP::TEST; use TOP::TEST::EXTENDONE; use TOP::TEST::EXTENDTWO; my $thing = new TOP::TEST( var_1 => 'this', var_2 => 'that', something +=>'extra'); # This is imported into the base class from EXTENDONE print $thing->print_one()."\n"; print $thing->{'var_3'}."\n";
Instead of daisy chaining them together passing the base class as a parameter of the new() for each extended class. Cheers to a wise helping hand!

Replies are listed 'Best First'.
Re: Inheritance automation help
by GrandFather (Saint) on Apr 05, 2012 at 01:17 UTC

    Reworking your sample code somewhat should help you see the light:

    #!/usr/bin/perl use strict; use warnings; package Top; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; $self->{var1} = 'Top set var1'; $self->{var3} = 'Top set var3'; return $self; } sub doPrint { my ($self) = @_; print "Test: $self->{var1}\n"; } package Top::Extend1; push @Top::Extend1::ISA, 'Top'; sub doPrint { my ($self) = @_; print "Extend1 overrides doPrint: $self->{var1}\n"; } package Top::Extend2; push @Top::Extend2::ISA, 'Top'; sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(%params); $self->{var1} = 'Top::Extend2 set var1'; return $self; } package main; my $test = Top->new(); my $one = Top::Extend1->new(something => 'extra'); my $two = Top::Extend2->new(); $test->doPrint(); $one->doPrint(); $two->doPrint();

    Prints:

    Test: Top set var1 Extend1 overrides doPrint: Top set var1 Test: Top::Extend2 set var1

    The main part of the trick is using push @Current::Package::ISA, 'Base::Package'; to hook up the inheritance chain. There are other ways to do that using parent or Exporter, but for light weight OO the push is sufficient.

    True laziness is hard work
      You have just enlightened me! I spend a few hours chewing through your example, and researching the why and how in the context I'm using it. I have my code switched over to an iteration of this, and I'm just trying to work out one last kink of it. What would I need to do to use the above examples packages with a main that looks like this?
      package main; my $test = Top->new(passed1=>'this is shared'); my $one = Top::Extend1->new(); my $two = Top::Extend2->new(); $test->doPrint(); $one->doPrint(); $two->doPrint(); print "Extra test: $test->{passed1}\n"; print "Extra one: $one->{passed1}\n"; print "Extra two: $two->{passed1}\n";
      And have the result say "this is shared" for each Extra Test. I think with that nugget of knowledge I should be able to tie the rest of my questions up on my own. Thanks again for the great response!

        You can't. At least, what you are asking for is not what class inheritance does. It is some sort of run time information sharing and there are lots of ways of doing that depending on what you actually want to achieve. It maybe that a clonish constructor does what you want (but probably not, and there are nasty gotchas):

        #!/usr/bin/perl use strict; use warnings; package Top; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; $self->{var1} = 'Top set var1'; $self->{var3} = 'Top set var3'; return $self; } sub clonish { my ($class, $other, %params) = @_; return bless {%$other, %params}, $class; } sub doPrint { my ($self) = @_; my $type = ref $self; print "Test ($type): $_ => $self->{$_}\n" for keys %$self; } package Top::Extend1; push @Top::Extend1::ISA, 'Top'; sub doPrint { my ($self) = @_; my $type = ref $self; print "Override ($type): $_ => $self->{$_}\n" for keys %$self; } package Top::Extend2; push @Top::Extend2::ISA, 'Top'; sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(%params); $self->{var1} = 'Top::Extend2 set var1'; return $self; } package main; my $test = Top->new(passed1 => 'this is copied'); my $one = Top::Extend1->clonish($test); my $two = Top::Extend2->clonish($test, something => 'extra'); $test->doPrint(); $one->doPrint(); $two->doPrint();

        Prints:

        Test (Top): var3 => Top set var3 Test (Top): var1 => Top set var1 Test (Top): passed1 => this is copied Override (Top::Extend1): var3 => Top set var3 Override (Top::Extend1): var1 => Top set var1 Override (Top::Extend1): passed1 => this is copied Test (Top::Extend2): var3 => Top set var3 Test (Top::Extend2): var1 => Top set var1 Test (Top::Extend2): passed1 => this is copied Test (Top::Extend2): something => extra

        If you tell us more about the problem domain we may be able to offer a better solution. Quite likely you are trying to use the wrong tool for the problem at hand and the monk's wider experience may be able to give a better approach.

        True laziness is hard work
        have the result say "this is shared" for each Extra Test
        I'm not quite sure what you require but, adding to Grandfather's first code example, this may help:
        package Top; # ... sub shared { my ($self) = @_; my $type = ref $self; print "Test ($type): This is shared\n\n"; } # ... package main; # ... print "Extra test:\n"; $test->shared(); print "Extra one:\n"; $one->shared(); print "Extra two:\n"; $two->shared();
        This will print:
        Extra test: Test (Top): This is shared Extra one: Test (Top::Extend1): This is shared Extra two: Test (Top::Extend2): This is shared
Re: Inheritance automation help
by natelewis (Novice) on Apr 05, 2012 at 15:42 UTC
    I got a little further with this, and feel a little better about it. I think what it comes down to is that I was trying to do something that inheritance isn't able to accomplish innately - and for some reason I thought I could wiggle it around to get it to work. Here is another concept I was working on to accomplish the above goal:
    use Top; my $test = new Top (Foo=>'Start Foo'); use Top::Extend1; $test = plugin Top::Extend1 ($test, %settingsScopedToExtend1); use Top::Extend2; $test = plugin Top::Extend2 ($test, %settingsScopedToExtend2);
    The passing of test to each one holds the class, it gets mashed together, altered slightly depending on what the Extend class is doing and passed back to $test with extra methods attached and altered class variables. The end result is that I needed to append classes to each other. So at any time I can call my base class and have new methods that were not there before, but were added because of calling its constructor named plugin that does a "cloneish" concept. Feel free to let me know if I'm off my rocker on doing something like this! Or there is a GOTCHA that I'm not thinking of that isn't obvious like accidental overwriting of methods and such. Also here is an example of what the Extend1 plugin constructor would look like in the Extend1 Package:
    sub plugin { my ($class, $other, %params) = @_; # # bless my new combined class # $other = bless $other, $class; # # set defaults scoped to Extend1 # $other->{"Extend1"}->{"someSetting"} = 'its value'; # # overwrite defaults # @{$other->{"Extend1"}}{keys %params} = values %params; # # update any $other data # $other->{'Foo'} .= ' Appended to it '; # # pass back our extended class # return $other; }

      You still haven't told us what you are trying to achieve, but it looks like you are setting up a test suite with pluggable tests. I'd structure it somewhat differently by having a test harness class (your Top class) and pluggable classes (your ExtendX classes). You may want a pluggable base class that knows how to hook itself up to the test harness, but most likely you'd do that by creating pluggable class instances and registering them with the harness. Something like this:

      #!/usr/bin/perl use strict; use warnings; package Harness; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; return $self; } sub register { my ($self, $plugin) = @_; push @{$self->{plugins}}, $plugin; } sub run { my ($self) = @_; my @results; for my $test (@{$self->{plugins}}) { print "Running ", $test->name(), "\n"; push @results, $test->run($self); } print "$_\n" for @results; } package PluginBase; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; return $self; } sub name { my ($self) = @_; return ref $self; } sub run { my ($self) = @_; return "Skipped " . $self->name() . " test - no run override provi +ded"; } package WinkTest; push @WinkTest::ISA, 'PluginBase'; sub run { my ($self) = @_; my $type = ref $self; return "Wink, wink. Wink test passed OK"; } package BlinkTest; push @BlinkTest::ISA, 'PluginBase'; package main; my $test = Harness->new(); $test->register (WinkTest->new()); $test->register (BlinkTest->new()); $test->run();

      Prints:

      Running WinkTest Running BlinkTest Wink, wink. Wink test passed OK Skipped BlinkTest test - no run override provided

      Please note the PackageName->constructor() style used to invoke package methods. The old constructor PackageName() style is pretty much deprecated because it is prone to parsing difficulties.

      True laziness is hard work
        I just finished up cleaning up the mess I created. The end result was a combination of using test case registering, and because of the structure and portability of what I'm working with I had to add @ISA for what I was calling the "parent" inside what I was calling the "child". My end result of this discussion took me A LOT further than I ever thought I was going to get with it! This isn't something I would ever do as design, but here is an example in concept of what I did:
        #!/usr/bin/perl use strict; use warnings; package Top; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; return $self; } sub firstSub { my ($class) = @_; print "FIRST SUB!\n"; } package Top::Extend1; push @Top::ISA, 'Top::Extend1'; sub addOnSub { my ($self) = @_; print "ADD ON\n"; } package Top::Extend2; push @Top::ISA, 'Top::Extend2'; sub secondAddOn { my ($self) = @_; print "SECOND ADD ON\n"; } package main; my $test = Top->new(); $test->firstSub(); $test->addOnSub(); $test->secondAddOn();
        So the way I was saying it was backwards making my question confusing to what my actual goal was. I'm sure this is very "hand slapish" behavior and complicated when it comes to overridden methods... but that was the key to solving my dilemma. Thanks for the help and enlightenment Monks!
        Thanks for the great post GrandFather. Sorry, I didn't mean to avoid describing correctly what I'm attempting to achieve. It was more of the matter of not knowing the right question to ask and the way to say it. I'm working on a internal content creation project that needs plug-able modules that add extra methods and change class variables, without ever knowing that it happened. The end result would be executing code that creates dynamic content against the new class that had the modules plugged into it that would use the new methods, and use the newly changed class variables. I just got back from traveling so I'm back to my project to get some more progress on it. I'll post my new result once I'm completed so you can see your wisdom induced idea. I truly appreciate your help and have learned a great deal from your posts, I think your last test suite idea will be ideal for what I'm trying to achieve!