Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Nested Classes

by dcorbin (Sexton)
on Mar 02, 2001 at 18:20 UTC ( [id://61799] : perlquestion . print w/replies, xml ) Need Help??

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.

Replies are listed 'Best First'.
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?

    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.

      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 } }
        Before going further, you might want to give Test::Unit a shot and see if it will work for you.
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

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 :)
      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

        The OP asked how to
        1. create an object,
        2. bless,
        3. and then bind subroutines to it (written within another objects method) as methods.

        That's what I tried to show. Sure it's better to define classes statically. But as I understood the OP, he wanted to do this on the fly