Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Custom import for alternate module APIs

by blssu (Pilgrim)
on Sep 09, 2002 at 14:51 UTC ( [id://196289]=note: print w/replies, xml ) Need Help??


in reply to Re: How to morph a plain module to OO (use a compatibility layer)
in thread How to morph a plain module to OO

A function-based API can be (almost) automatically generated from methods -- all you need is a customized import sub. A default object is created during import and each package using the module has a private default object. This approach usually works a bit better than Flexx's because (1) it avoids creating lots of temporary objects, and (2) there's a long-lived object around to hold module state, so all the state-setting methods still work.

I wouldn't recommend the CGI.pm module for studying this technique. CGI.pm is difficult to understand because of its long and glorious history. Take a look at my code below, read the docs on packages and symbolic references, look at Exporter.pm and then you're ready to crack open CGI.pm.

## MyClass.pm package MyClass; use strict; my @export = qw(verbose isFile); sub new { my ($class, %args) = @_; my $self = {verbose => $args{verbose} || 0}; return bless $self, $class; } sub verbose { my $self = shift; $self->{verbose} = shift if @_; return $self->{verbose}; } sub isFile { my ($self, $file) = @_; my $found = -f $file; if ($self->verbose) { print STDERR "$file ", ($found ? 'found' : 'not found'), "\n"; } return $found; } sub import { my $module = shift; my $mode = shift || ':methods'; if ($mode eq ':functions') { no strict 'refs'; my ($package) = caller(); my $object = MyClass->new(@_); foreach my $name (@export) { my $sub = $MyClass::{$name}; *{$package.'::'.$name} = sub { &{$sub}($object, @_) }; } } } 1; ## test.pl package verbose_main; use strict; use MyClass (':functions', verbose => 1); sub test { if (isFile('MyClass.pm')) { print "ok\n"; } if (verbose()) { print "verbose mode on\n"; } } package quiet_main; use strict; use MyClass (':functions'); sub test { if (isFile('MyClass.pm')) { print "ok\n"; } verbose(1); if (isFile('NotFound')) { print "ok\n"; } } package oo_main; use strict; use MyClass; my $env = MyClass->new(verbose => 1); sub test { if ($env->isFile('MyClass.pm')) { print "ok\n"; } } package main; use strict; verbose_main::test(); quiet_main::test(); oo_main::test();

Replies are listed 'Best First'.
Re: Custom import for alternate module APIs
by Flexx (Pilgrim) on Sep 09, 2002 at 22:08 UTC

    Cheers, ++blssu!

    First off: I like your solution! So, just because I like this discussion, allow me to throw in a few more thoughts.

    (1) it avoids creating lots of temporary objects

    Granted, my quick example forwarded this impression. Yet, it would also be possible to create a "master" object in the BEGIN block of the compatibility module, on which the modules' subroutines operate. (Shomehow though, in the example given, I didn't see very much sense in a class implementation in the first place, but I'm sure this was just an exemplary snipplet to begin with).

    (2) there's a long-lived object around to hold module state, so all the state-setting methods still work.

    If the old module uses package globals like $MyModule::verbose to store state information, I'll be not easy to provide this functionality (for both of our solutions) if you want to have these as properties in the object interface. Besides keeping them as class globals (properties), I could only imagine tackling this problems by tie-ing those variables with method calls (but admittedly, I have never tried this, never needed this).

    Your solution appears to focus on (and succeeds/excel in) creating an elegant class/module hybrid which strength is it's flexibility to provide whichever interface the caller expects. This implies to some extent, a direct (old) function to (new) method translation.

    To clarify: The mindset and propagated essence of my idea is not to create an OO version of the original module, but a set of (probably more specialized, pure OO) classes, that exaust the abilities of the original module and beyond, possibly even implementing things quite differently. With other words: Keep the focus on the improvements you want to archive, and avoid carrying old burdens as much as you can (or at least bundle them together and shift them to something "external").

    Then, in a somewhat independent effort (which could well be carried out by another developer/team), build the compatibility layer you need for your old modules. The whole idea behind this, as I'd anticipate from what Rudif layed out, was to enable more than maintenance -- enhanced development -- of a module that has reached it's limits within the bounds of it's current design pattern, while minimizing maintenance effort for older applications.

    Having to build (and maintain) an interface module which doesn't really implement anything appears to be a bearable burden to me, especially if you can settle on an OO model and interface contract for the new module class(es) beforehand.

    I wouldn't recommend the CGI.pm module for studying this technique. CGI.pm is difficult to understand because of its long and glorious history. Take a look at my code below, read the docs on packages and symbolic references, look at Exporter.pm and then you're ready to crack open CGI.pm.

    To be honest, CGI.pm was the only module having a hybrid interface that came to my mind at once. Probably there are better (simpler) examples, that was why I yelled for help naming more hybrid modules/techniques.

    So long,
    Flexx

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://196289]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (6)
As of 2024-04-19 17:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found