Perl:如何从基类导入子例程?

时间:2012-10-19 16:59:24

标签: perl oop inheritance import base

我有一个名为Foo :: Base的基类,我需要继承它的方法,比如'new',并在范围中导入一些子例程名称:

package Foo::Base;

sub new { ... }

sub import {
    no strict 'refs';

    my $caller = caller;

    *{"${caller}::my_sub"} = sub { 1 };
}

1;

所以,我需要在我的第二个类Foo :: Child:

中使用这个基类
use base 'Foo::Base';

...它适用于继承,但它不会在范围中导入“my_sub”。我可以添加字符串

use Foo::Base;

对它而言有帮助,但我不想写这样的东西:

use base 'Foo::Base';
use Foo::Base;

这看起来很奇怪......对这个问题有什么建议吗?

3 个答案:

答案 0 :(得分:14)

有两个原因可能是你想做你正在做的事,两者都不好。

首先,您出于某种原因尝试从父类导入方法。也许你误解了OO是如何工作的。你不需要这样做。只需将继承的方法称为方法,除非这些方法做得很古怪,否则它会正常工作。

更有可能这是一个混合使用模块,其中一些是方法,一些是导入函数。为此你可以做...

use base 'Foo::Base';
use Foo::Base;

你正确地观察到它看起来很奇怪......因为它有点奇怪。同时出口的一个类是混合成语,这将导致奇怪的使用模式。

最好的办法是重新设计类而不是导出函数,将函数拆分为自己的模块,或者将它们作为类方法。如果这些功能真的与课程没什么关系,那么最好将它们分开。如果他们确实与班级有关,那就让他们成为班级方法。

use base 'Foo::Base';

Foo::Base->some_function_that_used_to_be_exported;

这消除了接口不匹配,作为奖励,子类可以像任何其他方法一样覆盖类方法行为。

package Bar;

use base 'Foo::Base';

# override
sub some_function_that_used_to_be_exported {
    my($class, @args) = @_;

    ...do something extra maybe...

    $class->SUPER::some_function_that_used_to_be_exported(@args);

    ...and maybe something else...
}

如果您无法控制基类,您仍然可以通过编写将导出的函数转换为方法的子类来使界面变得清晰。

package SaneFoo;

use base 'Foo::Base';

# For each function exported by Foo::Base, create a wrapper class
# method which throws away the first argument (the class name) and
# calls the function.
for my $name (@Foo::Base::EXPORT, @Foo::Base::EXPORT_OK) {
    my $function = Foo::Base->can($name);
    *{$name} = sub {
        my $class = shift;
        return $function->(@_);
    };
}

答案 1 :(得分:6)

当您撰写use base时,您正在使用 base 模块的工具。并且你传递了你想成为你的基类的模块的参数。

在OO IS-A关系中,不需要导入。您可以使用OO模式调用方法:$object_or_class->method_name( @args )。有时候这意味着你不关心调用者是谁,如下:

sub inherited_util {
    my ( undef, @args ) = @_;
    ... 
}

sub inherited2 { 
    shift;
    ...
}

但是,如果你想使用在基本模块中定义的实用程序,从该模块中定义的类行为继承,那么这正是两个使用语句所指示的内容。

但是,如果您希望在模块中使用两种不同类型的行为,则最好将实用程序类型拆分为自己的模块,并从两个模块中使用它。无论哪种方式,显性行为通常优于隐式行为。

然而,我之前使用过这种模式:

sub import { 
    shift;
    my ( $inherit_flag ) = @_;
    my $inherit 
        = lc( $inherit_flag ) ne 'inherit' ? 0
        : shift() && !!shift()             ? 1
        :                                    0
        ;
    if ( $inherit ) { 
        no strict 'refs';
        push @{caller().'::ISA'}, __PACKAGE__;
        ...
    }
    ...
}
就这样,我用一个明确的用法捆绑进行一次调用。

use UtilityParent inherit => 1, qw<normal args>;

答案 2 :(得分:1)

为了澄清一点“但为什么不在Foo :: Child 中导入?”的问题,这里有一个程序会说明实际发生的事情:

use Foo::Child;
print my_sub(); # Really? Yes, really!
print Foo::Child::my_sub()," done\n";

并将其添加到Foo :: Base :: import()例程:

print "Caller is $caller\n";

当你运行它时,你会看到它作为输出:

Caller is main
1
Undefined subroutine &Foo::Child::my_sub called at foo_user.pl line 4.

我们看到Foo :: Base报告,让我们知道调用者是谁:这是主程序!我们看到'1',它证明是,main :: my_sub现在存在,然后失败,因为导入到了错误的命名空间。

这是为什么?因为导入过程全部由主程序处理。 Foo :: Base的导入不会被Foo :: Child中的任何东西调用;在加载模块的过程中,主程序会调用它。如果你真的,真的想强制sub,而不是方法,导入Foo :: Child,你需要在Foo :: Child本身明确地实现它。 'use Foo :: Base'会这样做,因为这会导致导入以Foo :: Child作为调用者执行;如果你坚持导入,但是双重使用会浪费你太多,你可以在'use base'之后立即调用Foo :: Base :: import()。这正是双重“使用”的目的。

尽管如此,我更喜欢Schwern的课程方法,我建议另类。