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

Greetings all.

Once upon a time I had a bunch of objects that all were included by one generic driver class. Each of these modules could provide some number of methods that the generic driver needs to know about, so that it would know to map a url to each method in the object.

Each of these 'page' objects may be written by any number of different programmers for any number of different projects. And all these programmers may be of different Perl skill levels.

So, the idea is to make the creation of these 'page' objects as simple and as straight forward as possible. I didn't want to have a 'provides' function that you had to list the public methods in because the first thing that is going to happen is that someone will leave one out, or mispell one, etc, etc.

Here is the solution I've come up with. Each 'page' class inherits from a parent class that uses a little symbol table evil to probe the child class and prepare the provides list for the driver object. I didn't do the probe in the driver because the symbol table trick made strict unhappy, so I used a very small base class that I could afford not to have strict in. It seems to do exactly what I need, but I'd like to have y'all's (pardon my southerner) feedback on the technique.

#/usr/bin/perl use strict; use inherited; use Data::Dumper; my $inherited = inherited->new(); print Dumper $inherited->{'provides'};
here is the parent object
package parent; sub new { my $class = shift; $class = ref($class) || $class; my $self = {}; $self->{'provides'} = mk_provides($class); bless($self, $class); return $self; } sub mk_provides { my $class = shift; # these are all the internal ones I've seen so far my %internal = map { $_ => 1 } qw (import isa ISA new BEGIN EN +D); $class .= "::"; local *stash; # here's the part that makes strict scream *stash = *{$class}; my @methods; foreach (keys %{*stash} ) { # if it's not an internal one or one that starts with +_ # add it to the list of public methods unless (defined $internal{$_} || $_ =~ /^_/) { push(@methods,$_); } } return \@methods; } 1;
and the 'page' object
package inherited; use strict; use base ("parent"); sub foo { } sub bar { } sub baz { } sub _private_method() { } 1;
And the output I get is exactly what I want.
$VAR1 = [ 'bar', 'foo', 'baz' ];
Any suggestions or thoughts are welcome.

/\/\averick

Replies are listed 'Best First'.
Re: Request for review: OO, inheritance, and a symbol table trick.
by chromatic (Archbishop) on Jul 02, 2001 at 06:40 UTC
    One other thing to note is that anything in the symbol table (scalar, array, hash, format, or filehandle besides sub) will show up in *stash. The trick is to see if there's anything defined for the CODE slot of the glob:
    foreach (keys %{*stash} ) { next if (defined $internal{$_} || /^_/); my $sub = *{$stash->{$_}}{CODE}; next unless defined &$sub; push(@methods,$_); }
    That might require a little tweaking, but you'll learn a lot. :) Oh, and if you think you'll eventually inherit from one of these driver classes, you might want to have mk_provides call SUPER() and weed out duplicates. Could come in handy.
Re: Request for review: OO, inheritance, and a symbol table trick.
by bikeNomad (Priest) on Jul 02, 2001 at 03:04 UTC
    Your users can still screw it up by not calling the parent class new() method (if they overload new()). One strategy you may want to use instead may be to use your parent package's import() method to find out what packages are using the parent package, then inspecting their @ISA to see if they're inherited, and then grabbing the information from their symbol table. Assuming they don't dynamically create subs after BEGIN time, this should work fine.

    You may also want to check for AUTOLOAD and DESTROY. Also, you can disable strict checking for a tiny scope:

    sub mk_provides { no strict 'refs'; # rest of the sub }
    and the strictness will be turned off for just the scope of the sub mk_provides.

    update: added comment about inheritance.

Re: Request for review: OO, inheritance, and a symbol table trick.
by clemburg (Curate) on Jul 02, 2001 at 14:08 UTC

    One additional thing to be aware of is that not all of the code entries you find in the symbol table will be methods of the class/object. That is, somebody could just put in a helper function in the package that you can't call as an object method.

    That is, you could have stuff like this in the child package:

    package SimpleExample; sub method_foo { my $self = shift; # ... bla bla bla ... my $arg = 42; my $intermediate_result = helper_function($arg); # ... bla bla bla ... } # ... sub helper_function { my ($arg) = @_; # ... bla bla bla ... }

    Now guess what a simple symbol table scan will give you: you will get out both "method_foo" and "helper_function" as code entries in the symbol table. But you can't say SimpleExample->helper_function($bar). "helper_function" is just a normal function, not a method.

    Solution: you will have to test any function names you got from the symbol table of the package if they can be called on the class/object. You do that by using the much underestimated "can()" method from package UNIVERSAL.

    Ah, and before I forget it: obviously you will have to do a recursive symbol table scan to get all the methods for a given package, scanning the parents of a class/object as well.

    Here is how something similar is done in Test::Unit::TestCase, part of the Test::Unit framework (coded by brother pdcawley, thanks again Piers!).

    sub list_tests { my $class = ref($_[0]) || $_[0]; my @tests; no strict 'refs'; if (defined(@{"$class\::TESTS"})) { push @tests, @{"$class\::TESTS"}; } else { push @tests, grep { /^test/ && $class->can($_) } keys %{"$class\::"}; } push @tests, map {$_->can('list_tests') ? $_->list_tests : ()} @{"$class\::ISA"}; my %tests = map {$_ => ''} @tests if @tests; return keys %tests; }

    (And yes, I have been there and messed it up. Thanks to Piers Cawley for educating me.)

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com