dcorbin has asked for the wisdom of the Perl Monks concerning the following question:
For certain types of code (unit testing with mock objects), it
is handy to implement an entire "class" within the test.
What I would like to do, is to create an object, bless, and
then bind subroutines to it (written within another objects
method) as methods. I don't know if this is doable in Perl
or not, but I'm hoping someone here can tell me.
Re (tilly) 1: Nested Classes
by tilly (Archbishop) on Mar 02, 2001 at 20:33 UTC
|
There is probably a better way to do this. (Likely on CPAN.) Plus this is slow. But here is a very simple implementation of a different inheritance scheme than the one that Perl uses. This is based on each object being its
own class. Call the def method to add new methods to the
object. Call new to generate an object that inherits from
the parent. Call "call" to call methods you have inherited,
or just call them directly. There is a basic search mechanism. Multiple inheritance is not supported. Methods
follow the usual Perl semantics. (The first argument is the object, the rest are arguments to the method.)
package ObjectClass;
use Carp;
use strict;
use vars qw($AUTOLOAD);
sub call {
my $self = shift;
my $meth = $self->find_meth(shift);
$meth->(@_);
}
sub def {
my $self = shift;
{
my $meth = shift;
my $implement = shift;
$self->{meth_cache}->{$meth} = $implement;
redo if @_;
}
}
sub find_meth {
my $self = shift;
my $meth = shift;
my $cache = $self->{meth_cache};
if (exists $cache->{$meth}) {
return $cache->{$meth};
}
elsif (exists $self->{proto}) {
return $self->{proto}->find_meth($meth);
}
else {
confess ("Object does not implement method '$meth'");
}
}
sub new {
my $proto = shift;
# Note for Merlyn, this time it is not cargo-cult programming! :-P
unless (ref($proto)) {
my $class = $proto;
$proto = { meth_cache => {}, };
bless($proto, $class);
}
my $self = { meth_cache => {}, proto => $proto };
bless $self, ref($proto);
}
sub AUTOLOAD {
my $self = shift;
$AUTOLOAD =~ s/.*:://;
return if $AUTOLOAD eq "DESTROY";
$self->call($AUTOLOAD, @_);
}
package main;
# A simple test
my $parent = new ObjectClass;
$parent->def("hello", sub {print "Hello world\n"});
my $child = $parent->new();
$child->def("goodbye", sub {print "Goodbye\n"});
$parent->hello(); # Calling the defining object
$child->hello(); # Inherit the method
$child->goodbye(); # Method in the subclass
$parent->goodbye(); # Blows up.
Does this make your mock classes easier to implement?
UPDATE
I did this mostly because I thought it would be fun to
write. If you want to use this idea, I would suggest
looking at Class::Classless or Class::SelfMethods. | [reply] [d/l] |
|
Thanks. I think that the might achieve what I was after. Since there was some confusion
about what I really want to do, and why, I'll try to give you an example, but
I'm going to have to show it in pseudo-java.
public class Foo
{
public void doSomething(Bar bar)
{ ... }
}
now, I want to write code that tests Foo.doSomething. I want that
test to be independent of any problems that Bar might have. So,
I write this...
public class FooTest
{
testDoSomething()
{
class MockBar extends Bar
{
// code to simulate a "Bar"
}
...
bar = new MockBar();
foo = new Foo();
foo.doSomething(bar);
// check that the right things happened to bar
}
}
| [reply] [d/l] [select] |
|
Before going further, you might want to give Test::Unit a shot and see if it will work for you.
| [reply] |
|
Re: Nested Classes
by arturo (Vicar) on Mar 02, 2001 at 19:12 UTC
|
If this is a question about inheritance, yes, Perl supports method inheritance. Put @ISA = qw(Parent); in the child class (see perldoc perltoot or perltoot, or check the Tutorials on this site).
What I'm not understanding, though, is the notion of a "subroutine within a method." In Perl, a method IS just a special kind of subroutine (one that expects an object as its first argument), and no subroutine can
contain a subroutine (well, you can do
sub a {
# stuff
sub b {
# more stuff
}
}
, but b has the same scope as a. I suppose you could have a code ref (reference to an anonymous subroutine) defined within a subroutine, but then the only way you're going to get at such a beastie outside the method under normal circumstances
is to have the coderef returned by the method (note: if you have a coderef visible outside of the method, then the subroutine is not really contained in the method either).
If you want to use the anonymous sub from outside the object's inheritance hierarchy (or on its own from within the inheritance hierarchy),
you're probably better off using the Real Coder© 's method of code re-use, which the uninitiated call "cut n' paste" =)
Does that help?
Philosophy can be made out of anything. Or less -- Jerry A. Fodor | [reply] [d/l] [select] |
Re: Nested Classes
by Tyke (Pilgrim) on Mar 02, 2001 at 19:19 UTC
|
Well, yes but I'm not quite sure why you'd want to do this.
Is this what you want?
#!perl -w
use strict;
my $a = 'foo';
my $b = \$a;
bless $b, 'bar';
sub bar::f1 {
my $self = shift;
print "f1 prints [$$self]\n";
}
*bar::f2 = sub {
my $self = shift;
print "f2 prints [$$self]\n";
};
$b->f1();
$b->f2();
Update: Changed PRE tags to CODE tags as per chipmunk's request.
Sorry, won't do it again :) | [reply] [d/l] |
|
Here's a slightly more verbose version of the same thing:
#!/usr/bin/perl -w
use strict;
package foo;
sub new{
my $m = {};
bless $m;
return $m;}
package bar;
sub new{
my $b = {};
bless $b;
return $b;}
sub corny {
return 'sparky';
}
package main;
my $s = foo->new();
my $r = bar->new();
$s->{'spark'} = \&bar::corny;
print $s->{'spark'}();
1;
This essentially calls a method out of a different package. I added the class stuff as window dressing. If you are looking to call and actual method from an instantiated object, just call a function and use a reference to the object as a parameter (called delegation in certain circles) to call the function. Lastly, use inheritance. If these things don't help, I am not sure what you are attempting to accomplish.
Celebrate Intellectual Diversity | [reply] [d/l] |
|
| [reply] |
|
|