I posted a few days ago about a dynamic inheritance problem I had encountered and the workaround I had adopted. Thanks to the feedback I received, I've switched to a new approach.
As I described, I was trying to use mixin classes to add combinations of optional functionality on top of a base class:
+---------+ +---------+ +---------+ | Filter | | Safe | | Base | +---------+ +---------+ +---------+ | | | +------------------+------------------+ v +-------------+ | Safe_Filter | @ISA=qw(Filter Safe Base); +-------------+
The problem lay in re-dispatching method calls via SUPER:: when a mixin class did not directly inherit from the base class.
As several people pointed out, there are alternatives to the SUPER mechanism, such as NEXT. Once I came to terms with the fact that the SUPER:: dispatch system is somewhat arbitrary, I realized that the easiest solution is just to use a custom method to perform the redispatch. If instead of calling $self->SUPER::method( @_ ) I had my mixins call $self->SUPER('method', @_ ), then I could intercept each of those calls and control which method got called next.
In the end I named this method NEXT, because it is reminiscent of the NEXT module, although the direct method call syntax avoids the need for AUTOLOAD.
For example, here's how the exception-catching mixin used to work:
BEGIN { push @MIXIN, "#line ".__LINE__.' "'.__FILE__.'"', "", <<'/' +} sub compile { eval { local $SIG{__DIE__}; (shift)->SUPER::compile( @_) }; } /
Here's the equivalent method using the new calls:
sub compile { eval { local $SIG{__DIE__}; (shift)->NEXT('compile', @_) }; }
The base class provides this implementation for the NEXT method:
sub NEXT { my ( $self, $method, @args ) = @_; my $calling_package = caller(0); my @classes = ref($self) || $self; my @isa; while ( my $class = shift @classes ) { push @isa, $class; no strict; unshift @classes, @{ $class . "::ISA" }; } while ( my $class = shift @isa ) { last if ( $class eq $calling_package ) } while ( my $class = shift @isa ) { next unless my $sub = $class->can( $method ); return &$sub( $self, @args ); } Carp::croak( "Can't find NEXT method" ); }
Does anyone know how to reliably extract the name of the calling method, so that I can ommit that from the calls to the NEXT method? The ( caller(0) )[3] can be hidden inside evals and such; do I just walk back through stack frames until I find one that's not an eval?
Thanks again for your feedback!
In reply to Solving the SUPER problem in Mixins with a Dispatch Method by simonm
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |