【问题标题】:Perl: How to import subroutines from base class?Perl:如何从基类导入子程序?
【发布时间】:2012-10-10 08:19:20
【问题描述】:

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

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

【问题讨论】:

    标签: perl oop inheritance import base


    【解决方案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 的类方法,并且我推荐这种替代方法。

    【讨论】:

      【解决方案2】:

      一个人想要做你正在做的事情可能有两个原因,它们都是不好的。

      首先是你试图从你的父类中导入方法......出于某种原因。也许你误解了 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->(@_);
          };
      }
      

      【讨论】:

      • 实际上我想在这些类中添加一些语法糖,就像在 Mojo::Base 中一样,但你是对的,谢谢 — 实现这对我当前的任务来说太过分了,如果我可以使用 Mojo::Base,我会这样做,但我不能。
      • 在这种情况下,请使用 use Foo -base 约定,如 Mojo::Base。我不建议走 use Foo 既导出又继承的 Moose 路线。这打破了 J::Random::Module 的太多惯例。除非这将成为您自己项目的 Moose 等价物。太好了……为什么“不能”使用 Mojo::Base?
      【解决方案3】:

      当您编写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>;
      

      【讨论】:

      • 是的,这与 Mojo::Base 或 Class::Simple 非常相似,谢谢!但我决定放弃更好的代码架构 =)
      猜你喜欢
      • 2013-07-06
      • 1970-01-01
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多