perlquestion
tlm
<p>I've been taking my first steps with [merlyn]'s [mod://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 [mod://Class::Prototyped]-type inheritance. In fact, I can't figure out how inheritance works with <c>CGI::Prototype</c>!</p>
<readmore>
<p>The script and module below illustrate the problem.</p>
<p>I'll describe the script first. Most of it is assignments and definitions. Things don't get moving until the call to <c>activate</c> at the very end. Users of <c>CGI::Prototype</c> will find this pattern familiar. They will also recognize the methods defined in package <c>Zero</c> in the middle block as being among those called by <c>CGI::Prototype::activate</c>.</p>
<code>
# 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__
</code>
<p>Passing the string 'Prototype' as the first argument to the script will cause <c>Zero</c> to be a subclass of <c>CGI::Prototype</c>. (The package I define below is called <c>CGI::Classic</c>, so 'Classic' is another meaningful value to pass as the first argument to the script.)</p>
<p>The <c>dispatch</c> method, which is called by the inherited <c>activate</c> method, returns 'One' if there are no CGI parameters, and 'Two' otherwise. Eventually this will result in the execution of either <c>One->render</c> or <c>Two->render</c>, as the case may be. The <c>render</c> method as defined in <c>Zero</c> prints the caller and the CGI params, if any.</p>
<p>In its next-to-last step the script defines another subclass of <c>Zero</c>, called 'MY_Zero'. Finally, it calls <c>activate</c> on the class passed as the script's <em>second</em> argument. Subsequent arguments, if any, should be in the form of 'key=value' strings.</p>
<p>With 'Prototype' as first argument, things work as expected if the second argument is 'Zero':</p>
<pre>
% perl cgi.pl Prototype Zero
One
% perl cgi.pl Prototype Zero foo=1 bar=2
Two
foo: 1
bar: 2
</pre>
<p>...but bomb if the second argument is 'MY_Zero':</p>
<pre>
% 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.
</pre>
<p>To show what I had <em>expected</em> to happen, I defined <c>CGI::Classic</c>. This module is, first, a <em>drastically</em> simplified toy version of <c>CGI::Prototype</c>. In particular, its (tiny) <c>activate</c> method doesn't even follow the original's logic (e.g. no testing of the result of the <c>respond</c> method, etc.). But the most fundamental difference between <c>CGI::C</c> and <c>CGI::P</c> is that <c>CGI::C</c> does not use [mod://Class::Prototype] at all:</p>
<code>
# 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__
</code>
<p>Now the script works with both 'Zero' and 'MY_Zero' as second argument.</p>
<pre>
% 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
</pre>
<p>I guessed that the problem resulted from the fact that <c>Class::Prototyped</c> 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:</p>
<c>
shift; # discard script's second argument
Zero->new( 'parent*' => 'Zero' )->activate;
</c>
<p>...but I get the same error as before.</p>
<p>Here's where I run out of steam. I find the innards of <c>Class::Prototyped</c> pretty difficult to understand, and I'm not even sure that that's were the problem is.</p>
<p>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.</p>
<p>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.</p>
<p>(Something tells me I'm going to learn a lot of Perl soon...)</p>
</readmore>
<div class="pmsig"><div class="pmsig-439528">
<p><small>the lowliest monk</small></p>
</div></div>