Re: 元のメソッドを保存しつつオーバーライドしたい

亀レスれすがどなたも書いてなさそうだったので。

とあるメソッド(Foo#greet)を呼ぶ前にちょっとごにょごにょ前処理的なことをしたい。

前処理した後は、オリジナルのメソッドを呼びたい。

でも、メソッド名はそのまま(greet)でコールしたい。

元のメソッドを保存しつつオーバーライドしたい - (ひ)メモ

Step 1 としてはこんな感じですかねぇ。

#!/usr/bin/perl

use strict;
use warnings;

package Foo;

sub new {
    my $class = shift;
    return bless { @_ }, $class;
}

sub greet {
    my ($self, $msg) = @_;
    my ($pkg, $file, $line) = caller;
    print "called from ${pkg}: ${file}(${line})\n";
    print "Hello, ", $self->{name}, ", ", $msg, "\n";
}

package main;

sub override_foo {
    my $old_sub = \&Foo::greet;
    my $new_sub
        = sub {
            my ($self) = @_;
            $self->{name} .= ' san';
            return &$old_sub(@_);
        };

    {
        no warnings 'redefine';
        *Foo::greet = $new_sub;
    }
}

override_foo();

my $o = Foo->new( name => 'ank' );

$o->greet('Nice to meet you.');
$o->greet('See you again.');

変えたところは,

  • full namespace で書けば,package をいちいち変える必要がない
  • ケースバイケースかもしれないけど,元関数を元パッケージに保存せずに一時変数に保存するようにした
  • no warnings ... とかはできるだけ狭い範囲で影響するようにする
    • さもないと新しい sub 内部にも適用されちゃう
    • まぁ no strict じゃなければそこまで気遣う必要はないかもですが

実行結果は

called from main: greet.pl(28)
Hello, ank san, Nice to meet you.
called from main: greet.pl(28)
Hello, ank san san, See you again.

となります。で,場合によって気になるのは,sub greet() での caller の値がオーバライドしている部分になってしまうことです。ここで Step 2 として AUTOLOAD の際などに良く使われる goto を使います。

といっても,

            return &$old_sub(@_);

となっている部分を

            goto &$old_sub;

にするだけ。

これで実行結果は,

called from main: greet.pl(41)
Hello, ank san, Nice to meet you.
called from main: greet.pl(42)
Hello, ank san san, See you again.

となり,無事 Foo->greet() してるところが caller になりました。


ほんとは Foo に手を入れられるんなら miyagawa さんの Class::Trigger を使うとか OOP 的に Foo::Extended を作ってそっちを使うとかいろいろ手はありますが。でも既存のコードになるべく手を加えずにフックしたいという状況もあるのは確か。

そういう状況のためにより汎用的にした,

みたいなモジュールもあるにはあるみたいですが,古くてあんまり盛り上がってないので品質がどうかはわかりません。