2009-09-01 34 views
14

你可以在Perl中拦截方法调用,对参数做些什么,然后执行它?是否可以拦截Perl方法调用?

+1

我看到你使用了标签“aop”。为什么不问你想使用的具体AOP技术? (你的问题的答案可能会帮助你实现一个AOP系统,但为什么当别人已经做到了?) – jrockway 2009-09-01 23:19:45

+0

我认为这可以用方法属性完成,但现在我找不到合适的perldoc页面(perldoc属性不是我想到的页面)。我记得一个很好的例子,涉及包装添加日志中的各种方法... – Ether 2009-09-02 00:39:50

+0

请参阅此相关的SO问题的其他信息:http://stackoverflow.com/questions/635212/how-i-redefine-perl-class-methods – draegtun 2009-09-06 15:08:31

回答

14

是的,你可以拦截Perl子程序调用。我在Mastering Perl中有关于这类事情的整章。查看Hook::LexWrap模块,该模块可让您在不通过所有细节的情况下执行此操作。 Perl的方法只是子例程。

你也可以创建一个子类并覆盖你想要捕获的方法。这是一个稍微好一些的方法,因为这就是面向对象编程希望你做到的方式。但是,有时候人们会编写不允许您正确执行此操作的代码。 Mastering Perl还有更多关于这方面的内容。

6

这看起来像Moose的工作! Moose是Perl的一个对象系统,可以做到更多。 docs在解释方面会做得比我更好,但您可能需要的是Method Modifier,特别是before

+1

麋是你用于整个应用程序的东西,而不是有针对性的问题。 – 2009-09-01 22:08:50

+3

但这取决于问题是什么,问题的提问者没有说明任何细节。这可能是某人评估创建新应用程序时要采取的方法,在这种情况下,开启Moose将是一个合适且有益的响应。 – jsoverson 2009-09-01 23:03:28

+1

我们只是说了同样的事情。我没有说不使用穆斯,但我也没有说要使用它。 – 2009-09-02 00:53:09

7

简而言之,Perl具有修改符号表的能力。您可以通过程序包所属的包的符号表调用子程序(方法)。如果您修改符号表(并且这不是很脏),则可以用调用您指定的其他方法替换大多数方法调用。这证明了方法:

# The subroutine we'll interrupt calls to 
sub call_me 
{ 
    print shift,"\n"; 
} 

# Intercepting factory 
sub aspectate 
{ 
    my $callee = shift; 
    my $value = shift; 
    return sub { $callee->($value + shift); }; 
} 
my $aspectated_call_me = aspectate \&call_me, 100; 

# Rewrite symbol table of main package (lasts to the end of the block). 
# Replace "main" with the name of the package (class) you're intercepting 
local *main::call_me = $aspectated_call_me; 

# Voila! Prints 105! 
call_me(5); 

这也表明,一旦有人需要子程序的引用,并通过引用调用它,你再也不能影响这样的电话。

我很确定有框架在perl中进行方位化,但是我希望这个方法能够证明这种方法。

3

是的。

您需要三样东西:

的参数来调用在@_这只是另一种动态范围的变量。

然后,goto支持reference-sub参数,该参数保留当前的@_但是进行另一个(尾部)函数调用。

最后local可用于创建词汇范围的全局变量,符号表被埋在%::

所以,你有:

sub foo { 
    my($x,$y)=(@_); 
    print "$x/$y = " . ((0.0+$x)/$y)."\n"; 
} 
sub doit { 
    foo(3,4); 
} 
doit(); 

这当然打印出:

3/4 = 0.75 

我们可以通过local foo换成去:

my $oldfoo = \&foo; 
local *foo = sub { (@_)=($_[1], $_[0]); goto $oldfoo; }; 
doit(); 

而现在我们得到:

4/3 = 1.33333333333333 

如果你想修改*foo不使用它的名字,而你不想通过操纵%::使用eval,那么你可以修改它,例如:

$::{"foo"} = sub { (@_)=($_[0], 1); goto $oldfoo; }; 
doit(); 

而现在我们得到:

3/1 = 3 
5

你可以和帕维尔描述了一个好办法做到这一点,但你应该详细说明,为什么你想要做这个摆在首位。

如果您正在寻找拦截对任意子例程的调用的高级方法,那么摆弄符号表将适合您,但是如果您想为可能导出到您当前使用的命名空间的函数添加功能,那么您可能需要知道如何调用其他名称空间中存在的函数。

例如,Data :: Dumper通常会将函数'Dumper'导出到调用名称空间,但您可以覆盖或禁用该函数并提供您自己的Dumper函数,然后通过完全限定名称调用原始函数。

例如

use Data::Dumper; 

sub Dumper { 
    warn 'Dumping variables'; 
    print Data::Dumper::Dumper(@_); 
} 

my $foo = { 
    bar => 'barval', 
}; 

Dumper($foo); 

再次,这是一个替代的解决方案取决于原始问题,即可能更合适。使用符号表时可以带来很多乐趣,但如果你不需要它可能会导致难以维护的代码。

+1

在这种情况下,你应该给Dumper一个空的进口清单,所以你不要输入任何东西。另外,在这种情况下序列很重要。您必须先导入,然后覆盖。最后的子程序定义获胜。最后,在警告下,你会得到一个“重新定义”的错误。 – 2009-09-02 00:55:38

+0

当然,只是为了举例而保持简单。直接对直接有更多的价值(对响应者来说更快)比解释每个切线细节。无论如何,导入和警告的概念将被最好地解释为其他问题。 – jsoverson 2009-09-02 01:15:33