http://qs321.pair.com?node_id=550334

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

I've been taking my first steps with merlyn's CGI::Prototype, which I find pretty nice overall, but I'm running into a conceptual block here. In summary: inheritance doesn't work as I expect. Not even Class::Prototyped-type inheritance. In fact, I can't figure out how inheritance works with CGI::Prototype!

The script and module below illustrate the problem.

I'll describe the script first. Most of it is assignments and definitions. Things don't get moving until the call to activate at the very end. Users of CGI::Prototype will find this pattern familiar. They will also recognize the methods defined in package Zero in the middle block as being among those called by CGI::Prototype::activate.

# cgi.pl use strict; use warnings; @One::ISA = @Two::ISA = 'Zero'; { package Zero; use base 'CGI::' . shift; sub dispatch { shift->param ? 'Two' : 'One'; } sub render { print my $self = shift, "\n"; print "$_: " . $self->param( $_ ) . "\n" for $self->param; } } @MY_Zero::ISA = 'Zero'; shift->activate; __END__

Passing the string 'Prototype' as the first argument to the script will cause Zero to be a subclass of CGI::Prototype. (The package I define below is called CGI::Classic, so 'Classic' is another meaningful value to pass as the first argument to the script.)

The dispatch method, which is called by the inherited activate method, returns 'One' if there are no CGI parameters, and 'Two' otherwise. Eventually this will result in the execution of either One->render or Two->render, as the case may be. The render method as defined in Zero prints the caller and the CGI params, if any.

In its next-to-last step the script defines another subclass of Zero, called 'MY_Zero'. Finally, it calls activate on the class passed as the script's second argument. Subsequent arguments, if any, should be in the form of 'key=value' strings.

With 'Prototype' as first argument, things work as expected if the second argument is 'Zero':

% perl cgi.pl Prototype Zero
One
% perl cgi.pl Prototype Zero foo=1 bar=2
Two
foo: 1
bar: 2

...but bomb if the second argument is 'MY_Zero':

% perl cgi.pl Prototype MY_Zero
One
Content-type: text/plain

ERROR: Two->initialize_CGI not called at /usr/lib/perl5/CGI/Prototype.pm line 173.

To show what I had expected to happen, I defined CGI::Classic. This module is, first, a drastically simplified toy version of CGI::Prototype. In particular, its (tiny) activate method doesn't even follow the original's logic (e.g. no testing of the result of the respond method, etc.). But the most fundamental difference between CGI::C and CGI::P is that CGI::C does not use Class::Prototype at all:

# CGI/Classic.pm use strict; use warnings; { package CGI::Classic; use CGI; my $CGI; sub activate { $CGI = CGI->new; shift->dispatch->render; } sub param { shift; $CGI->param( @_ ) }; sub dispatch { die 'subclass responsibility' } sub render { die 'subclass responsibility' } } 1; __END__

Now the script works with both 'Zero' and 'MY_Zero' as second argument.

% perl cgi.pl Classic MY_Zero
One
% perl cgi.pl Classic MY_Zero foo=1 bar=2
Two
foo: 1
bar: 2
% perl cgi.pl Classic Zero baz=3 frobozz=4
Two
baz: 3
frobozz: 4

I guessed that the problem resulted from the fact that Class::Prototyped uses a model of inheritance that is different from Perl's standard model, so I also tried a version of the script in which the last call to activate has the following form:

shift; # discard script's second argument Zero->new( 'parent*' => 'Zero' )->activate;

...but I get the same error as before.

Here's where I run out of steam. I find the innards of Class::Prototyped pretty difficult to understand, and I'm not even sure that that's were the problem is.

I ran into this problem when I tried to write a test script for some CGIP-based classes. I wanted to override some methods of the main class for testing, and that's when I created a class analogous to 'MY_Zero' above.

So I have a question and a comment. The question is: how do I do what I want to do (i.e. create a subclass of my main class for the purpose of overriding its methods for testing)? The comment is that the problem illustrated above strikes me as pretty serious, because it completely defeats reasonable expectations about how inheritance should work.

(Something tells me I'm going to learn a lot of Perl soon...)

the lowliest monk