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

I have questions regarding how to make a module behave intelligently in two particular situations. I would be appreciative of any insights that can be provided, or references that can be pointed out that would be appropriate to the subjects.

Situation 1: Suppose that there are two modules, which we will refer to as Foo::Bar and Baz::Quux. I wish to write a module (Blarg::Wibble) which may deal with an object produced by either of these two modules. I know that I could probably write something like Blarg::Wibble::Foo_Bar and Blarg::Wibble::Baz_Quux, each of which able to work on one kind of object or the other. How could I make it so that another programmer would only have to 'use Blarg::Wibble' and pass it the object (created from either Foo::Bar or Baz::Quux), and have the module be smart enough to use the appropriate functions from either Blarg::Wibble::Foo_Bar or Blarg::Wibble::Baz_Quux?

Situation 2: During the 'make test' phase of the install process, how would one go about making it so that if Baz::Quux was missing that it would skip testing for that, but would be able to work later if, in the interim, Baz::Quux had been installed?

-----
(WRT - With Respect To)

Update: Thanks to bart for letting me know that I had, while editing, left out part of the first sentence of the first paragraph. Corrected.

  • Comment on Question about creating intelligent behaviors in modules WRT external objects/modules

Replies are listed 'Best First'.
Re: Question about creating intelligent behaviors in modules WRT external objects/modules
by bart (Canon) on Feb 21, 2006 at 08:47 UTC
    For some real life examples of what I think you are after, look how the Crypt::* block cipher modules work, see Crypt::CBC for the root module. Or as another example, DBI and its DBD::* drivers. Both of these root modules serve as a gateway to the other, "driver" modules.

    What's so strange about Perl is that you don't need subclasses of a common base class, in order to use "virtual methods", unlike in many other OO languages. In other words, in Perl you can do

    $obj->toot(@args)
    irrespective of whether $obj is a Foo::Bar or a Baz::Quux; these two classes don't even need to have anything in common, all that is necessary is that both know how to toot. So, perhaps you're just trying too hard...?

    Alternatively, can also think about "delegating" methods, where you pass on method calls to embedded objects. Class::Delegate is one obvious example of such a delegation module, but I'm no expert in this area and I don't know it's the best you can find.

    As for the tests: yes there are ways to skip tests if a module isn't installed at test time. That won't prohibit code using a later installed "driver", perhaps even one that didn't exist yet, at the time the master module was installed. There simple is no connection between the tests and any later use of the code.

    The only problem is, when adding the driver later, that it won't have been tested. Perhaps you should include your tests with the driver, or if it's not a module specifically created to work with yours, with an interface compatibility layer module. The latter could have contain no actual code apart from some stubs, like a module loader, and the tests.

Re: Question about creating intelligent behaviors in modules WRT external objects/modules
by GrandFather (Saint) on Feb 21, 2006 at 06:37 UTC

    Using isa:

    if (UNIVERSAL::isa($ref, 'Foo::Bar')) { #... } elsif (UNIVERSAL::isa($ref, 'Baz::Quux')) { #... }

    DWIM is Perl's answer to Gödel
Re: Question about creating intelligent behaviors in modules WRT external objects/modules
by diotalevi (Canon) on Feb 21, 2006 at 06:25 UTC

    Query your object to see if it provides the necessary API. If so, use it. If not, try the fallback. If that doesn't exist, bitch at the user.

    sub fulfills_interface_foo { my $self = shift; return $self->can( ... ) and $self->can( ... ) and $self->can( ... ) and $self->can( ... ); }

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Question about creating intelligent behaviors in modules WRT external objects/modules (factor)
by tye (Sage) on Feb 21, 2006 at 07:21 UTC

    Presumably, you have a lot of code that is common between the two cases (else it likely wouldn't make much sense to combine these case into a single module).

    So, best would be to factor out the code that isn't the same (normally you 'factor out' what is the same). In the places where these chunks were factored out, you put a $obj->Method(...). Now write Method() for the two cases but both wrapper classes have the same interface.

    After you've defined the two versions of each method, you'll have two classes. Each is a wrapper for one of the external classes.

    A common next step would be to make @Blarg::Wibble::Foo_Bar::ISA= 'Blarg::Wibble' and then have Blarg::Wibble::new() decide which subclass to bless into based on the type of argument passed in.

    But OO old-timers learn to be suspicious of inheritance. And this is a perfect example of a trap where inheritance makes your design a bit more fragile and harder to extend.

    Better is just to have Blarg::Wibble::new() create and hold a Blarg::Wibble::Foo_Bar-wrapper that it uses.

    For testing, you want to 'skip' tests in the case you described. How you skip tests depends on the testing module you used. For example, Test::More offers the following somewhat dubious construct:

    SKIP: { skip $why, $how_many if ! eval { require Foo::Bar; 1 }; # tests go here };

    For your case, I'd probably have a FooBar.t and BaxQuux.t that each use a common test.pm whose import() runs the tests and does skip_all if the passed-in module name can't be required.

    - tye