为非对象perl模块复制base / parent.pm功能的最简洁方法是什么?

时间:2010-08-11 19:51:31

标签: perl refactoring module

我现在不太清楚,可能会忽略一些简单的事情。我已经考虑了一段时间并一直在寻找,但不能再想到任何明智的搜索查询会导致我找到我想要的东西。

简而言之,我想知道如何进行模块继承,就像base.pm/parent.pm为面向对象模块做的那样;仅适用于基于Exporter的模块。

我的意思的一个假设的例子:

这是我们的脚本。它最初加载了Foo.pm并从中调用了baz(),但是baz()有一个可怕的bug(我们很快就会看到),所以我们现在使用Local / Patched / Foo.pm来解决这个问题。我们这样做,因为在这个假设的情况下我们无法改变Foo(它是一个正在积极开发的cpan模块,你看),并且它是巨大的(严重的)。

#!/usr/bin/perl

# use Foo qw( baz [... 100 more functions here ...] );
use Local::Patched::Foo qw( baz [... 100 more functions here ...] );
baz();

这是Foo.pm.正如你所看到的,它导出了调用qux的baz(),它有一个可怕的bug,导致事情崩溃。我们希望保留baz和Foo.pm的其余部分,而不需要进行大量的复制粘贴,特别是因为它们可能会在以后更改,因为Foo仍在开发中。

package Foo;
use parent 'Exporter';
our @EXPORT = qw( baz [... 100 more functions here ...] );
sub baz { qux(); }
sub qux { print 1/0; }            # !!!!!!!!!!!!!
[... 100 more functions here ...]
1;

最后,由于Foo.pm用于很多地方,我们不想使用Sub :: Exporter,因为这意味着将一个bandaid修复程序复制粘贴到所有这些地方。相反,我们正在尝试创建一个新的模块,其行为和看起来像Foo.pm,并且实际上仍然从Foo.pm加载其99%的功能,只是用更好的一个替换丑陋的qux子。

如果Foo.pm是面向对象的,那么这样的事情会是什么样的:

package Local::Patched::Foo;
use parent 'Foo';
sub qux { print 2; }
1;

现在这显然不适用于我们目前的情况,因为parent.pm只是没有这样做。

是否有一种干净简单的方法来编写Local / Patched / Foo.pm(使用任何适用的CPAN模块),这种方法可以工作,而不是手动复制Foo.pm的函数命名空间?

6 个答案:

答案 0 :(得分:4)

如果它是一个你要覆盖的子程序,你可以做一些猴子修补:

*Foo::qux = \&fixed_qux;

我不确定这是否是最干净或最好的解决方案,但是对于临时权宜之计,直到上游修复qux中的错误,它应该这样做。

答案 1 :(得分:3)

只需添加另一种方法来修补Foo的{​​{1}} qux函数,这个函数没有任何手动的类型地球操作。

package Local::Patched::Foo;
use Foo (); # load but import nothing

sub Foo::qux {
    print "good qux";
}

这是有效的,因为Perl的包总是可变的,只要上面的代码在加载Foo.pm后出现,它就会覆盖现有的baz例程。您可能还需要no warnings 'redefine';来消除任何警告。

然后使用它:

use Local::Patched::Foo;
use Foo qw( baz );

baz();  # calls the patched qux() routine

您可以通过在use中编写自定义导入方法来取消两个Local::Patched::Foo行,如下所示:

# in the Local::Patched::Foo package:

sub import {
    return unless @_;             # return if no imports
    splice @_, 0, 1, 'Foo';       # change 'Local::Patched::Foo' to 'Foo'
    goto &{ Foo->can('import') }; # jump to Foo's import method
}

然后就是:

use Local::Patched::Foo qw( baz );

baz();  # calls the patched qux()

答案 2 :(得分:1)

一种方法是简单地替换子参考。如果可以安装,请使用Sub::Override CPAN模块。没有这个,这将做:

package Local::Patched::Foo;

use Exporter;

sub baz { print "GOOD baz!\n" };

sub import() {
    *Foo::baz = \&Local::Patched::Foo::baz;
}

1;

答案 3 :(得分:1)

而不是劫持亚历山大的答案(这是正确的,但不完整),这是一个单独副本的解决方案:


package Foo;
use Exporter 'import';
our @EXPORT = qw(foo bar baz qux);
our %EXPORT_TAGS = (
    'all' => [ qw(foo bar baz qux) ],
    'all_without_qux' => [ qw(foo bar baz) ],
);

sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub qux { 'qux' }

1;

package Foo::Patched;
use Foo qw(:all_without_qux);
use Exporter 'import';
our @EXPORT = qw( foo bar baz qux );

sub qux { 'patched qux' }

1;

package main;
use Foo::Patched;

print qux();

您也可以在程序中使用use Foo; ,只要您在Foo::Patched 之前使用,或者您将使用原始破解版本覆盖已修补的qux。

这里有一些道德(至少他们是恕我直言):

  1. 如果未明确告知(即保持@EXPORT为空,请不要导出到调用者的名称空间中;并使用@EXPORT_OK%EXPORT_TAGS来允许调用者准确指定他们想要的内容。或者,根本不要导出,并为所有库函数使用完全限定名称。
  2. 编写库,使函数称为OO样式:Foo->function而不是Foo::function。通过使用我们都知道和喜爱的标准use base语法,可以更轻松地覆盖函数,而无需使用monkeypatching符号表或操纵导出器列表。

答案 4 :(得分:0)

package Local::Patched::Foo;
use Foo qw/:all_without_qux/; #see Exporter docs for tags or just list all functions
use Exporter 'import'; #modern way
our @EXPORT = qw( baz [... 100 more functions here ...] qux);
sub qux { print 2; }
1;

答案 5 :(得分:0)

我建议您替换违规文件。

mkdir Something
cp Something.pm Something/Legacy.pm # ( or /Old.pm or /Bad.pm )

然后转到该文件并编辑包行:

package Something::Legacy;

然后您可以在遗留代码前面一步。创建一个新的Something.pm并获取它的所有输出:

use Something::Legacy qw<:all>;
our @EXPORT      = @Something::Legacy::EXPORT;
our @EXPORT_OK   = @Something::Legacy::EXPORT_OK;
our %EXPORT_TAGS = %Something::Legacy::EXPORT_TAGS;

在您当前的软件包中完成所有这些后,只需重新实现sub。

sub bad_thing { ... }

调用Something::do_something的旧代码将通过新模块调用旧代码。任何调用Something::bad_thing的遗留代码都将调用新代码。

同样,您可以通过其他方式操纵*Something::Legacy。如果您的代码未使用本地电话,那么您将不得不使用&Something::Legacy::bad_thing

my $old_bad_thing = \&Something::Legacy::bad_thing;
*Something::Legacy::bad_thing = \&bad_thing;

因此,如果需要,bad_thing仍允许使用该行为:

sub bad_thing { 
    ...
    eval { 
        $old_bad_thing->( @_ );
    };
    unless ( $EVAL_ERROR =~ /$hinky_message/ ) { 
        ...
    }
    ...
}