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

Hello Monks,

I am attempting to create a class hierarchy which, when new is called for the base class, the first parameter will allow a run time decision as to which derived class is required and instantiate that. Kind of like how DBI works. I would prefer to not specify all the classes required. Something like this:

use Astro::Observation; my $obs = Astro::Observation->new( "simple", ... ); my $obc = Astro::Observation->new( "complex", ... );
rather than this:
use Astro::Observation::Simple; use Astro::Observation::Complex; my $obs = Astro::Observation::Simple->new( ... ); my $obc = Astro::Observation::Complex->new( ... );
I don't have a clue how to go about doing this. I have Conway's book, but cannot figure out from there.

Can anyone suggest somewhere to look, or a pure perl module which uses this? I looked at DBI, but couldn't figure out how they accomplished this.

As you might be able to infer, this is my first attempt at using objects in Perl, and I'm barely treading water...

Thanks,
Rob

Replies are listed 'Best First'.
(jeffa) Re: Runtime instantiation decisions
by jeffa (Bishop) on Mar 30, 2002 at 17:01 UTC
    Ahhh, the Factory Pattern!

    All you need to do is make Astro::Observation the factory. It will return a class depending on what argument it receives. Here is a simple way to implement Astro::Observation:

    package Astro::Observation; use Astro::Observation::Simple; use Astro::Observation::Complex; sub new { my $self = shift; my $class = shift; return Astro::Observation::Simple->new(@_) if 'simple' eq $class; return Astro::Observation::Complex->new(@_) if 'complex' eq $class; return undef; }
    Which can be called like so:
    my $obs = Astro::Observation->new("simple",'foo'); my $obc = Astro::Observation->new("complex",'bar');
    But this way gets tiresome after you add more classes that the factory can deliver. I am quite sure that this can be avoided by using eval somehow.

    UPDATE: once again - just do what the wise and resourceful Kanji said.

    UPDATE x 2: that's what i was trying to do! :D
    Here is a tar ball should anyone want to try this out.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: Runtime instantiation decisions
by Kanji (Parson) on Mar 30, 2002 at 17:02 UTC

    As it happens, merlyn posted something similar the other day, which you could adapt easily (untested)...

    # in Astro::Observation sub new { my $self = shift; # Foo->new('bar',@_) => Foo::Bar->new(@_) my $class = __PACKAGE__ . "::" . ucfirst(shift); require autouse; autouse->import( $class, "$class\::new" ); return $class->new( @_ ); }

    Update:

    ... or not.

    After trying out the above, I kept getting the error "autouse into different package attempted", which appears to be a known bug in autouse when using a module that isn't at the root of it's namespace (ie, Foo would work, but Foo::Bar doesn't).

    Perhaps merlyn has a patched version of autouse?

    Anyhoo, another way to do it (and as jeffa suggests) is with eval...

    sub new { my $self = shift; # Foo->new('bar',@_) => Foo::Bar->new(@_) my $class = __PACKAGE__ . "::" . ucfirst(shift); eval "require $class;"; # Note: "" *NOT* {} ! if ( $@ ) { # whoops, there was an error warn( $@ ); # require'ing $class; perhaps return; # it doesn't exist? } return $class->new( @_ ); }

        --k.


      This is expensive and dangerous:
      eval "require $class;"; # Note: "" *NOT* {} !
      whereas this is cheaper and safer (no firing up of the compiler):
      (my $file = $class) =~ s#/#::#g; require $file;
      Please note that for future programs. (Yes, portabilty to macs and VMS is sacrificed.. for that, pull down File::Spec. But this works on Unix and Windows.)

      -- Randal L. Schwartz, Perl hacker

        Don't you mean something like this instead?
        (my $file = $class) =~ s^::^/^g; require "$file.pm";
        Now, this seems to work using file spec:
        my $file = File::Spec->catfile( split /::/, $class ) . ".pm";
        Thanks for the time and tips,
        LogicalChaos
Re: Runtime instantiation decisions
by LogicalChaos (Beadle) on Mar 30, 2002 at 18:37 UTC
    Thank you jeffa and Kanji for your wisdom and example today.

    jeffa, I notice that you may rid your code of

    use Astro::Observation::Simple; use Astro::Observation::Complex;
    in Astro/Observation.pm

    Thanks again,
    LogicalChaos

    More logical than chaotic now.