Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Abusing Exporter for Conditional Inheritance

by mojotoad (Monsignor)
on Mar 17, 2005 at 00:11 UTC ( [id://440207]=perlquestion: print w/replies, xml ) Need Help??

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

Hello fellow monks,

I've got a situation where I sometimes want my class to inherit from Class A, and somtimes from Class B, depending on user request.

Let's say, for example, that my class is a type of StoneGatherer class and that most of the time I just want to gather Blue stones. Every now and then, however, I want to gather Red stones. There is no real difference between how the stones are gathered, just in what type of stones are returned.

I do indeed have to inherit from a Gatherer class of some sort -- has_a won't cut it. (and for the curious here, the two Gatherer classes in question are HTML::Parser and HTML::TreeBuilder (which itself isa HTML::Parser)).

Obviously I could make two classes, StoneGatherer::Blue and StoneGatherer::Red, which inherit from Gatherer::Blue and Gatherer::Red, respectively. But given that there's no difference in the gathering process, only what is gathered, it seems sort of silly.

What I'd like to do is abuse Exporter, overriding import() in such a way that I can just do this:

# normal mode, @ISA = qw(Gatherer::Blue) use StoneGatherer;
vs this
# red mode, @ISA = qw(Gatherer::Red) use StoneGatherer qw(red);

Now, it also occurs to me that I'm thinking about the problem completely wrong -- as I'm sure all the OO purists out there are probably screaming right now. For example, this might be a case where a 'mixin' solution is preferred.

I'd like to know a) if it is wrong, how wrong is it, b) what's the 'right' way to think about the problem, and c) if there are multiple solutions, what are they?

Thanks for your time and thoughts,
Matt

P.S. As an aside, there are examples of abusing Exporter out there, such as with Test::More:

use Test::More tests => 42;

Any other examples out there? ;)

Update: in tracking down references, I came across the following illustration in the PerlDesignPatterns wiki regarding the AbstractClass pattern:

Explicitly using, then adding to @ISA, a marker interface is kind of ugly. One solution is to move part of the work to the abstract marker class:
package Item; sub import { push @{caller().'::ISA'}, __PACKAGE__; }
So, at least there's precedent out there for mussing around with @ISA using import().

Replies are listed 'Best First'.
Re: Abusing Exporter for Conditional Inheritance
by samtregar (Abbot) on Mar 17, 2005 at 00:53 UTC
    perrin's class factory suggestion sounds good, as long as the behavior changes globally. For per-object changes something like Class::MethodMaker's method proxying may be more appropriate. Basically you setup a normal composition relationship (StoneGatherer has a Gatherer) but the particular Gatherer object is held in an attribute. Then you define the list of methods that should be proxied to the Gatherer that's actually in use.

    This is something like the system used by Krang to manage the relationship between elements and element classes. Each element is assigned to an element class at runtime and the object behaves as though the element class is its base class via proxying. For example, this code:

    $element->publish();

    Is really:

    $element->class->publish(element => $element);

    -sam

Re: Abusing Exporter for Conditional Inheritance
by perrin (Chancellor) on Mar 17, 2005 at 00:19 UTC
    I dislike abusing Exporter (really the import method) and mix-ins. Your problem sounds pretty easy to solve with a class factory. Check out some of the class factory modules on CPAN.
      I'm not so sure that class factories are the right approach in this case. From the Class::Factory docs:
      Factory classes are used when you have different implementations for the same set of tasks but may not know in advance what implementations you will be using.
      In my case it's the reverse: I have the same implementation for a different set of tasks.

      Thanks for your thoughts,
      Matt

        A class factory can be a way to hand back the same class with different initialization parameters. The point is to hide the initialization stuff behind a simple call.

        In the case you describe here, if the code calling this thing knows which one it wants, I think you should actually either just make the two classes and call them or make a class that has some kind of method like "set_collection_color()" for switching the behavior.

Re: Abusing Exporter for Conditional Inheritance
by chromatic (Archbishop) on Mar 17, 2005 at 01:21 UTC

    As samtregar suggested, a better way to do this from an OO purity standpoint is with composition, not inheritance.

    However, this has little to do with Exporter as such; import() is merely the mechanism that runs optional code whenever you use a module. Conceptually, I agree with perrin that there's a semantic problem with pushing additional code here, but practically it's almost the only hook there is for such things and it's so useful that I use it often.

Re: Abusing Exporter for Conditional Inheritance
by tlm (Prior) on Mar 17, 2005 at 02:31 UTC

    I'm puzzling over my own HTML::TreeBuilder inheritance conundrum, so this question interests me, but I suspect that I don't quite follow, because my naïve inclination would be to pass the class of the Stone to the StoneGatherer's constructor.

    my $g = Gatherer->new('Stone::Red');

    P.S. As an aside, there are examples of abusing Exporter out there, such as with Test::More:

      use Test::More tests => 42;
    
    Any other examples out there? ;)

    SOAP::Lite seems to go pretty heavy on the ol' import.

    the lowliest monk

      This was my first inclination as well, but when you muck with @ISA, it affects all instances, rather than the particular one you're constructing. With perl, at least.

      I suppose you could localize @ISA, but I haven't tried that.

      Has anyone else? Is it just as much of a bastardization as what I've suggested above?

      Matt

      Update: this is of interest: Re: Re: Re (tilly) 1: Strict, strings and subroutines
      (thanks to Revelation for the node reference)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://440207]
Approved by Tanktalus
Front-paged by Revelation
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-03-29 15:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found