现代Perl:如何在AUTOLOAD()中实现Redispatching方法?

4

在面向对象编程方面感觉不够熟练,我试图通过阅读《现代Perl》这本书来提高自己。关于所问的话题,在这本书中我发现了以下示例:

package Proxy::Log; 

sub new
{
    my ($class, $proxied) = @_;
    bless \$class, $proxied;
}

sub AUTOLOAD
{
    my ($name) = our $AUTOLOAD =~ /::(\w+)$/;
    Log::method_call( $name, @_ );
    my $self = shift;
    return $$self->$name( @_ );
}

这段代码只是一个草稿还是一个可工作的示例?

我不明白,我应该如何使用它,在哪里以及它应该记录什么,我应该创建一个对象($proxied应该得到什么)?

我只添加了几行来测试它,但没有获得AUTOLOAD功能:

package main;

my $tst = Proxy::Log->new();
say $tst->AnyKindOfSub();

我希望你能给我提供一些关于it技术的有效代码。我以为我已经理解了闭包和AUTOLOAD的工作原理,但是现在我卡住了。

2个回答

8
正如所指出的,您在构造函数中颠倒了bless的参数顺序。虽然这是您代码的直接问题,但在编写redispatching方法时重要的考虑因素是使用goto &sub语法来擦除AUTOLOAD调用的堆栈帧:
sub AUTOLOAD
{
    my ($name) = our $AUTOLOAD =~ /::(\w+)$/;
    Log::method_call( $name, @_ );
    my $self = shift;
  # return $$self->$name( @_ );   # instead of this, use the following:
    unshift @_, $$self;           # setup argument list
    goto &{ $$self->can($name) }  # jump to method
}

如果redispatched方法使用caller内置函数(安装方法、本地化变量、Carp错误报告...)进行任何操作,则此技术将使caller正常工作。使用原始的return $$self->$name(@_)行将始终报告callerAUTOLOAD子例程的最后一行,这可能是难以发现的错误来源。
如果您想稍微改进错误报告,可以将最后一行编写为:
 goto &{ $$self->can($name) or Carp::croak "no method '$name' on $$self" };

假设已经加载了Carp包。

4

我认为这个例子在 Proxy::Lognew 中交换了 bless 参数。可能应该是:

bless \$proxied, $class;

以下是一个功能性的例子,它很可能是有意为之的。代理类会记录日志,然后重新分发调用到目标对象(在下面的示例中是Another类)。

package Proxy::Log;

sub new {
    my ($class, $proxied) = @_;
    bless \$proxied, $class;
}

sub AUTOLOAD {
    my ($name) = our $AUTOLOAD =~ /::(\w+)$/;
    warn "$name: @_";
    my $self = shift;
    return $$self->$name( @_ );
}

package Another;

sub new { 
    bless {}, $_[0];
} 

sub AnyKindOfSub {
    warn "Target called\n";
    return "Hello";
};


package main;

my $tst = Proxy::Log->new(Another->new);
say $tst->AnyKindOfSub();

Log::method_call 让我感到很困惑,现在我明白它只是一个真实日志方法的占位符。我曾经认为它会递归调用 AUTOLOAD,但我没有看到任何理由。谢谢,现在对我来说清晰多了! - w.k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接