【问题标题】:How can I apply a method modifier to a method generated by AUTOLOAD?如何将方法修饰符应用于 AUTOLOAD 生成的方法?
【发布时间】:2015-12-08 10:56:21
【问题描述】:

我有一个非常有趣的困境。我正在开发 CVS 存储库的 Perl 脚本接口,并创建了 Perl 对象来表示 ModulesPathsFiles。由于ModulesPathsFiles 都可以对它们发出CVS 命令,因此我设置了AUTOLOAD 例程以采用任何未识别的方法并在对象上发出它们,就好像它们是CVS 命令一样。

所有这些 CVS 命令的执行方式完全相同,但其中一些需要对输出进行特殊处理才能获得我想要的结果。


例如,我想在返回之前获取 diff 命令的输出并重新格式化它。

我用的是Moose,所以一般这种特殊处理可以如下进行:

after 'diff' => sub {
    # Reformat output here
}

问题是...我从未明确创建过diff 方法,因为它是由AUTOLOAD 生成的,而Perl 不允许我为它创建方法修饰符,因为它在技术上不存在!

有没有办法让它按我想要的方式工作?

【问题讨论】:

  • 如果您的 AUTOLOAD 子程序创建了被自动加载的符号(这样就不会再次为该符号调用 AUTOLOAD),这只是一个调用after 的问题(这只是一个普通的子程序)创建符号。
  • 符号创建后如何执行此操作?如果我只是尝试使用after,它会说在继承层次结构中找不到方法“diff”。
  • 那你没有创建符号(diff),或者你从错误的包中调用了after
  • 哦,你的意思是定义像*diff这样的东西?我没怎么弄乱符号。
  • 但是你正在编写一个 AUTOLOAD,其目的是“加载”符号......

标签: perl autoload moose method-modifier


【解决方案1】:

after 应用于您的AUTOLOAD 方法。

after 'AUTOLOAD' => sub {
    my $method = $The::Package::AUTOLOAD;
    $method =~ s/.*:://;
    if ($method eq 'diff') {
        # do  after diff  stuff
    } elsif ($method eq 'foo') {
        # do  after foo  stuff
    } else {
        # never mind, don't want to do anything after this function
    }
};

编辑:

我发现我可能想要更多地控制diff 命令,因此我在您的答案中添加了更多细节。希望有人会发现这些信息有用。

要获得更多控制权,您可以使用around

around 'AUTOLOAD' => sub {
    my $orig = shift;
    my $self = shift;
    (my $command = $AUTOLOAD) =~ s{.+::}{};

    # Special processing
    if ($command eq 'diff') {

        #
        # Add "before" special processing here
        #

        my $output = $self->$orig(@_);

        #
        # Add "after" special processing here
        #

    }
    else {
        return $self->$orig(@_);
    }
};

这允许您在调用函数之前和之后进行特殊处理。

欲了解更多信息,请参阅:Moose::Manual::MethodModifiers

【讨论】:

  • 我真的很喜欢这种方法,因为它将AUTOLOADed 命令的任何特殊处理都集中在一个地方。谢谢@mob!
【解决方案2】:

根据AUTOLOAD-using 类的实现程度,您可能会发现它也尊重can 方法,只需调用can 就足以创建该方法。

__PACKAGE__->can( "diff" );
after diff => sub { ... };

【讨论】:

  • 不幸的是,这似乎不适用于 Moose 类:(
【解决方案3】:

我建议您重新构建系统以使用特征,而不是依赖 AUTOLOAD 行为。如果您的行为没有分散在各处,可维护性和意图会更加明显。

例如,您可以通过以下方式执行您想要的操作:

package Trait::CVSActions;

use Moose::Role;

sub commit { print 'in commit for ' . shift . "\n" }

sub diff { print 'diffing for ' . shift . "\n" }

package Module;

use Moose;

with 'Trait::CVSActions';

package Path;

use Moose;

with 'Trait::CVSActions';

after commit => sub { print "after commit on Path\n" };

package main;

my $module = new Module;
my $path = new Path;

$module->commit;
$path->commit;

如果您希望使用 AUTOLOAD 来调度未知命令,那么这很危险,因为您可能需要对某些您不知道的特殊处理进行处理,因此您可能会导致自己未来的问题。

【讨论】:

  • 我会研究这个,但我使用AUTOLOAD 的主要原因是因为有很多 CVS 命令,我不想手动实现它们。如果试图调用一个无效的 CVS 命令,它会将命令传递给 CVS,CVS 将返回一个错误,指出它是一个无效的命令。
猜你喜欢
  • 2013-09-02
  • 1970-01-01
  • 2018-02-10
  • 2011-06-25
  • 1970-01-01
  • 2015-11-24
  • 1970-01-01
  • 2014-03-20
  • 1970-01-01
相关资源
最近更新 更多