Frage

Let's assume that I already have had both of Child package and Parent package with several subroutines. These two packages are combined together through aggregation just like in perltoot:

use warnings;
use strict;

package Child;

sub new {
    my ($class, %arg) = @_;
    return bless { %arg }, $class;
}

sub method_x {
    warn 'call method x';
}

sub method_y {
    warn 'call method y';
}

sub method_z {
    warn 'call method z';
}

1;


package Parent;

sub new {
    my ($class, %arg) = @_;
    return bless {
        child => undef,
        %arg,
    }, $class;
}

sub child { shift->{child} }
sub x { shift->child->method_x(@_) }
sub y { shift->child->method_y(@_) }
sub z { shift->child->method_z(@_) }

sub _callback {
    warn "I want to kick this callback after every child methods.";
}

1;


package main;

my $p = Parent->new(
    child => Child->new,
);

$p->x;
$p->y;
$p->z;

1;

After a while, I wanted to kick _callback for every Child's methods and I stunned at I was trying to add this callback to every wrapper methods(x/y/z).

Can I do this job more elegantly? Did I have to allow more flexibility to the package at the start? How?

Any advice is appreciated.

War es hilfreich?

Lösung

One possibility is to use method modifiers, which are provided by object systems like Moose or Moo:

use strict; use warnings;

package Child {
  use Moose;

  sub method_x { warn "call method_x" }
  sub method_y { warn "call method_y" }
  sub method_z { warn "call method_z" }
}

package Parent {
  use Moose;

  has child => (is => 'rw');

  sub x { shift->child->method_x(@_) }
  sub y { shift->child->method_y(@_) }
  sub z { shift->child->method_z(@_) }

  # A method modifier in action
  after [qw/ x y z /] => sub {
    warn "called after every Parent (!) invocation";
  };
}

my $p = Parent->new(child => Child->new);

$p->x; $p->y; $p->z;

Output:

call method_x at - line 7.
called after every Parent (!) invocation at - line 23.
call method_y at - line 8.
called after every Parent (!) invocation at - line 23.
call method_z at - line 9.
called after every Parent (!) invocation at - line 23.

If you actually wish to wrap all the methods of Child, use a subclass:

package WrappedChild {
  use Moose;
  extends 'Child';

  # the /(?=)/ regex matches always
  after qr/(?=)/ => sub {
    warn "called after each method in Child";
  };
}


my $p = Parent->new(child => WrappedChild->new);

$p->x; $p->y; $p->z;

This produces

called after each method in Child at - line 32.
called after each method in Child at - line 32.
called after each method in Child at - line 32.
called after each method in Child at - line 32.
called after each method in Child at - line 32.
called after each method in Child at - line 32.
call method_x at - line 7.
called after each method in Child at - line 32.
called after every Parent (!) invocation at - line 22.
call method_y at - line 8.
called after each method in Child at - line 32.
called after every Parent (!) invocation at - line 22.
call method_z at - line 9.
called after each method in Child at - line 32.
called after every Parent (!) invocation at - line 22.
called after each method in Child at - line 32.
called after each method in Child at - line 32.
called after each method in Child at - line 32.

which might be a bit excessive. Sticking to explicit names might be preferable.

See Moose::Manual::MethodModifiers for more info.


If you do not wish to use any modules, you can hack your way through the jungle symbol table:

for my $name (qw/method_x method_y method_z/) {
  no strict 'refs';
  no warnings 'redefine';
  my $orig = \&{"Child::$name"};
  *{"Child::$name} = sub {
    my @return_values = wantarray ? $orig->() : scalar $orig->();
    warn "called after each method";
    return wantarray ? @return_values : $return_values[0];
  };
}

Output:

call method_x at - line 7.
called after each method at - line 31.
call method_y at - line 8.
called after each method at - line 31.
call method_z at - line 9.
called after each method at - line 31.

Andere Tipps

package Wrapper;

use strict;
use warnings;

use Carp qw( );

sub wrap {
   my ($cb, $o) = @_;
   return bless({
      o  => $o,
      cb => $cb,
   });
}

sub AUTOLOAD {
   my $self = shift;
   my $o  = $self->{o};
   my $cb = $self->{cb};

   my ($method) = our $AUTOLOAD =~ /^.*::(.*)\z/;
   my $sub = ;
   if (!$o->can($method) && !$o->can("AUTOLOAD")) {
      my $package = ref($o);
      Carp::croak("Can't locate object method \"$method\" via package \"$pkg\"");
   }

   if (wantarray) {
      my @rv = $object->$method(@_);
      $cb->($method, @_);
      return @rv;
   }
   elsif (defined(wantarray)) {
      my $rv = $object->$method(@_);
      $cb->($method, @_);
      return $rv;
   }
   else {
      $object->$method(@_);
      $cb->($method, @_);
      return;
   }
}

my $w = wrap(sub { warn "Returning from $_[0]\n" }, Child->new);
$w->x; $w->y; $w->z;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top