6

我有一个名为 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;

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

4

3 回答 3

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->(@_);
    };
}
于 2012-10-19T18:30:05.530 回答
6

编写时use base,您正在使用base模块的功能。并且您正在向它传递您希望成为基类的模块的参数。

在 OO IS-A 关系中,不需要导入。您使用 OO 模式调用方法:$object_or_class->method_name( @args ). 有时这意味着你不在乎调用者是谁,就像这样:

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

或者

sub inherited2 { 
    shift;
    ...
}

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

尽管如此,如果您想在模块中使用两种不同类型的行为,最好将实用程序类型的东西拆分到它们自己的模块中,并从两个模块中使用它。无论哪种方式,显式行为通常都比隐式行为好。

但是,我以前使用过这种模式:

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>;
于 2012-10-19T17:41:47.203 回答
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 中的任何东西调用;它在加载模块的过程中被主程序调用。如果你真的,真的想强制 subs,而不是方法,导入到 Foo::Child,你需要明确地让它发生在 Foo::Child 本身。'use Foo::Base' 会这样做,因为这会使导入以 Foo::Child 作为调用者执行;如果您坚持导入,但双重使用让您过于兴奋,您可以在“使用基础”之后立即调用 Foo::Base::import()。无论如何,这正是双重“使用”所做的。

不过,我更喜欢 Schwern 的类方法,我推荐这种替代方法。

于 2012-10-19T21:29:41.927 回答