On Thu, 30 Oct 2003, Dave Rolsky wrote:

> First, I need a name.  I'm pretty sure this belongs under the Class::
> namespace, so here's a few I came up with:
>
>   Class::DynamicWrapper
>
>   Class::DynamicISA
>
>   Class::DynamicInheritance
>
>   Class::Chain

Well, I threw this together and it works, although I had to compromise and
make module do $self->super::foo in order to have custom dispatch, because
there is just no good way to make Perl do what I want that wouldn't be
absolutely hideous.

I'm attaching the code (temporarily called Class::DynamicWrapper) and the
test suite so people can see some examples, in case that helps.  WARNING:
This code does strange funky things, and may cause nausea, vomiting, or
migration to Python.

Note that I'm not wedded to _any_ of the names in here for packages or the
super::foo bits (it could be PARENT::foo or whatever), so I welcome
suggestions in that area.


-dave

/*=======================
House Absolute Consulting
www.houseabsolute.com
=======================*/


package Class::DynamicWrapper;

use strict;

use vars qw($VERSION);

$VERSION = 0.01;

my $base = 'MadeBy::Class::DynamicWrapper::Class';
my $num = 0;

# Given a set of classes like Foo::Base, Foo::Bar, and Foo::Baz, we
# end up with a hierarchy like this:
#
#              Foo::Base
#                 |
#        MadeBy::Class::DynamicWrapper::Class000000000
#                 |
#     Foo::Bar    |
#          \      |
#        MadeBy::Class::DynamicWrapper::Class000000001
#                 |
#     Foo::Baz    |
#          \      |
#        MadeBy::Class::DynamicWrapper::Class000000002
#
# Then with the strange jiggery-pokery in the super::AUTOLOAD method
# we arrange to dispatch calls to super::foo() made by Foo::Baz to
# MadeBy::Class::DynamicWrapper::Class000000001.  This latter class
# will either inherit foo() directly from Foo::Bar, or it will inherit
# it from Foo::Base.  Either way, the next relevant parent class is
# called.
#

sub hierarchy
{
    unless (@_)
    {
        require Carp;
        Carp::croak( "Cannot call hierarchy function with only a single class name.\n" 
);
    }

    my @parents;
    my @children;
    my $last;
    foreach my $class (@_)
    {
        my $name = _make_name();

        my @isa = ( $class, ( $last ? $last : () )  );

        {
            no strict 'refs';
            @{"$name\::ISA"} = @isa;
        }

        $last = $name;
    }

    return $last;
}

sub _make_name { sprintf( '%s%09d', $base, $num++ ) }

package super;

sub AUTOLOAD
{
    my $caller_class = caller();

    my $descendant_class = ref $_[0] || $_[0];

    my $class = $descendant_class;

    my $class_to_call;

    # I'm too lazy to write this in a saner way.  Basically we are
    # going up the inheritance tree looking at the "right" side
    while (1)
    {
        {
            no strict 'refs';
            $class_to_call = ${"$class\::ISA"}[1];
        }

        die "Cannot use super for classes not created by Class::DynamicWrapper\n"
            unless $class_to_call =~ /^MadeBy::Class::DynamicWrapper/;

        if ( $class_to_call->isa($caller_class) )
        {
            no strict 'refs';
            $class = ${"$class\::ISA"}[1];

            next;
        }

        last;
    }

    my $meth = join '::', $class_to_call, (split /::/, $super::AUTOLOAD)[1];

    return shift->$meth(@_);
}



-------------------------------------------------------------------------------

use strict;

use Class::DynamicWrapper;

use Test::More tests => 26;

{
    package Foo::Base;

    sub new { bless {}, shift }

    sub foo { 'base' }
}

{
    package Foo::Bar;

    sub foo { 'bar' }

    sub bar { 'also bar' }

    sub other { 'in bar' }
}

{
    package Foo::Baz;

    sub baz { 'baz' }

    sub other { 'in baz' }
}

{
    my $class = Class::DynamicWrapper::hierarchy( 'Foo::Base', 'Foo::Bar', 'Foo::Baz' 
);

    my $object = $class->new;

    foreach my $c ( qw( Foo::Base Foo::Bar Foo::Baz ) )
    {
        isa_ok( $object, $c );
    }

    foreach my $m ( qw( foo bar baz other ) )
    {
        can_ok( $object, $m );
    }

    is( $object->foo,   'bar' );
    is( $object->bar,   'also bar' );
    is( $object->baz,   'baz' );
    is( $object->other, 'in baz' );
}

{
    my $class = Class::DynamicWrapper::hierarchy( 'Foo::Base', 'Foo::Baz', 'Foo::Bar' 
);

    my $object = $class->new;

    foreach my $c ( qw( Foo::Base Foo::Bar Foo::Baz ) )
    {
        isa_ok( $object, $c );
    }

    foreach my $m ( qw( foo bar baz other ) )
    {
        can_ok( $object, $m );
    }

    is( $object->foo,   'bar' );
    is( $object->bar,   'also bar' );
    is( $object->baz,   'baz' );
    is( $object->other, 'in bar' );
}

{
    package Super::Base;

    sub new { bless {}, shift }

    sub meth { 'base' }
}

{
    package Super::Foo;

    sub new { bless {}, shift }

    sub meth { join ' ', 'foo', shift->super::meth() }
}

{
    package Super::Bar;

    sub new { bless {}, shift }

    sub meth { join ' ', 'bar', shift->super::meth() }
}

{
    my $class = Class::DynamicWrapper::hierarchy( 'Super::Base', 'Super::Foo', 
'Super::Bar' );

    is( $class->meth, 'bar foo base' );
}

{
    my $class = Class::DynamicWrapper::hierarchy( 'Super::Base', 'Super::Bar', 
'Super::Foo' );

    is( $class->meth, 'foo bar base' );
}

{
    package Super::Base;

    sub new { bless {}, shift }

    sub meth { 'base' }
}

{
    package Super::NoMeth;

    sub new { bless {}, shift }
}

{
    package Super::HasMeth;

    sub new { bless {}, shift }

    sub meth { join ' ', 'has', shift->super::meth() }
}

{
    my $class =
        Class::DynamicWrapper::hierarchy( 'Super::Base', 'Super::NoMeth', 
'Super::HasMeth' );

    is( $class->meth, 'has base' );
}

{
    my $class =
        Class::DynamicWrapper::hierarchy( 'Super::Base', 'Super::HasMeth', 
'Super::NoMeth' );

    is( $class->meth, 'has base' );
}

Reply via email to