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

I want to inherit a module based on a parameter given to the constructor. The simplified example below should illustrate what I'm trying to do, but it doesn't work (I assume because @ISA needs to be global). In the real module, the constructor will receive a filename, then inherit another module based on the file's extension.

Is it possible to make something like this work? Or are there better alternatives? Thanks.

use strict; use warnings; my $ob1 = foo->new('alpha'); my $ob2 = foo->new('beta'); $ob1->write; $ob2->write; package foo; sub new{ my $class = shift; our @ISA = (shift); return bless {}, $class; } sub write{ my $self = shift; print $self->read; } package alpha; sub read{ return "Alpha\n"; } package beta; sub read{ return "Beta\n"; }

Desired output:

Alpha Beta

Update: Thanks for the help everyone. I think I've got a handle on this now.

Replies are listed 'Best First'.
Re: Dynamically setting up inheritence at runtime
by kyle (Abbot) on Jan 26, 2009 at 20:14 UTC

    This doesn't work because @ISA is a class variable, not an instance variable. You're trying to change the behavior of instances within one class, so you'd have to do that with differences in the instances. On initialization, you can take note of what the "base" class is to be and then use that within the instance.

    package foo; sub new{ my $class = shift; my $base = shift; return bless { base_class => $base }, $class; } sub write{ my $self = shift; my $base = $self->{base_class}; print eval "\$self->$base\::read"; }

    I used eval because my other attempts at getting this to work didn't.

    no strict 'refs'; print $self->&{"$base\::read"}; # syntax error # string found where operator expected print $self->"$base\::read"; # executes but ignores $base's inheritance, if any no strict 'refs'; print &{"$base\::read"}( $self );

    This is not to say I recommend using eval for this. In fact, I think it's another argument that what you're trying to do isn't a very good idea.

    I'd encourage you to consider object composition instead of some funny inheritance.

    If you're dead set on this, you'll probably have to resort to something like what I did in Overloading different instances differently.. Every instance will be in its own package set up with its own @ISA that refers to both "Foo" and either "Alpha" or "Beta". Or something like that.

    This kind of works too:

    # code above package foo unchanged package foo::alpha; BEGIN { @foo::alpha::ISA = qw( foo alpha ); } package foo::beta; BEGIN { @foo::beta::ISA = qw( foo beta ); } package foo; sub new{ my $class = shift; my $base = shift; return bless {}, "$class\::$base"; } # all other code unchanged

    I can't say I recommend that either, but it's better than eval.

Re: Dynamically setting up inheritence at runtime
by ikegami (Patriarch) on Jan 26, 2009 at 20:09 UTC

    Have a function create an object of the appropriate type.

    use strict; use warnings; my $ob1 = My::File::Handler->new_handler('work.txt'); my $ob2 = My::File::Handler->new_handler('work.doc'); $ob1->write(); $ob2->write(); BEGIN { package My::File::Handler; sub new_handler { my ($class, $fn) = @_; my $ext = ...; # built from $fn my $pkg = ...; # built from $ext return $pkg->new($fn); } sub new{ my ($class, $fn) = @_; return bless {}, $class; } sub write{ my $self = shift; print $self->read; } sub read{ die "Abstract"; } } BEGIN { package My::File::Handler::txt; our @ISA = 'My::File::Handler'; sub read{ return __PACKAGE__; } } BEGIN { package My::File::Handler::doc; our @ISA = 'My::File::Handler'; sub read{ return __PACKAGE__; } }

    new_handler should probably be a method of another class.

    I'd probably use Module::Pluggable to load the appropriate handler.

      Thanks ikegami this is what I'm looking for. Now that I understand a little better I'll take a look at module::pluggable.

Re: Dynamically setting up inheritence at runtime
by kennethk (Abbot) on Jan 26, 2009 at 19:30 UTC

    Why don't you want to do something like this?

    use strict; use warnings; my $ob1 = foo->new('alpha'); my $ob2 = foo->new('beta'); $ob1->write; $ob2->write; package foo; sub new{ my $class = shift; my $subclass = shift; return bless {}, $subclass; } package alpha; sub read{ return "Alpha\n"; } sub write{ my $self = shift; print $self->read; } package beta; sub read{ return "Beta\n"; } sub write{ my $self = shift; print $self->read; }

    You could even prevent some of the rewriting by creating a base class to hold write.

Re: Dynamically setting up inheritence at runtime
by JavaFan (Canon) on Jan 26, 2009 at 20:45 UTC
    I'm going to assume you want a parent class "Foo", and subclasses Alpha, Beta, ... from Foo, to be determined at run time. You may want to use code similar to the following (untested!):
    use strict; use warnings; # Parent class. package Foo; sub new { my $class = shift; my $subclass = shift; { no strict 'refs'; push @{"${subclass}::ISA"}, "Foo" unless grep {$_ eq "Foo"} @{"${subclass}::ISA"} } bless {} => $subclass; } package Alpha; sub read {return "Alpha"} package Beta; sub read {return "Beta"} my $oa = Foo->new("Alpha"); my $ob = Foo->new("Beta");
Re: Dynamically setting up inheritence at runtime
by ikegami (Patriarch) on Jan 26, 2009 at 19:55 UTC

    [ Ignore this post. I missed the "In the real module" bit initially. I'll post something else in a minute. ]

    Close. Change

    my $ob1 = foo->new('alpha'); my $ob2 = foo->new('beta');
    to
    my $ob1 = alpha->new(); my $ob2 = beta->new();
    And set @ISA for alpha and beta.
    use strict; use warnings; my $ob1 = alpha->new() my $ob2 = beta->new() $ob1->write; $ob2->write; BEGIN { package foo; sub new{ my $class = shift; return bless {}, $class; } sub write{ my $self = shift; print $self->read; } } BEGIN { package alpha; our @ISA = 'foo'; sub read{ return "Alpha\n"; } } BEGIN { package beta; our @ISA = 'foo'; sub read{ return "Beta\n"; } }
Re: Dynamically setting up inheritence at runtime
by DrHyde (Prior) on Jan 28, 2009 at 10:33 UTC
    package Foo; use strict; use vars '@ISA'; sub new { my($class, %named_params) = @_; push @ISA, $named_params{parent_class}; # rest of the constructor goes here }