如何在Perl OO模块中实现调度表?

时间:2010-05-07 21:40:24

标签: perl oop

我想将一些OO包中的subs放入一个数组中 - 也在包中 - 用作调度表。像这样的东西

package Blah::Blah;

use fields 'tests';

sub new {
    my($class )= @_;

    my $self = fields::new($class);

    $self->{'tests'} = [
                         $self->_sub1
                        ,$self->_sub2
                       ];
    return $self;
}

_sub1 { ... };
_sub2 { ... };

我不完全确定这个的语法?

$self->{'tests'} = [
                         $self->_sub1
                        ,$self->_sub2
                       ];

$self->{'tests'} = [
                         \&{$self->_sub1}
                        ,\&{$self->_sub2}
                       ];

$self->{'tests'} = [
                         \&{_sub1}
                        ,\&{_sub2}
                       ];

我似乎无法在OO包中使用它,但它在程序方面非常简单,我没有找到OO的任何示例。

非常感谢任何帮助, 伊恩

4 个答案:

答案 0 :(得分:11)

你的朋友是can。它返回对子例程的引用(如果存在),否则返回null。它甚至可以正确地走向OO链。

$self->{tests} = [
    $self->can('_sub1'),
    $self->can('_sub2'),
];

# later

for $tn (0..$#{$self->{tests}}) {
    ok defined $self->{tests}[$tn], "Function $tn is available.";
}

# and later

my $ref = $self->{tests}[0];
$self->$ref(@args1);
$ref = $self->{tests}[1];
$self->$ref(@args2);

或者,由于this问题(恰好是this问题的变体),您可以直接调用它:

$self->${\$self->{tests}[0]}(@args1);
$self->${\$self->{tests}[1]}(@args1);

请注意,\为我们提供了对subref的引用,然后在${}之后$self->取消引用。呼!

为了解决大脑提到的及时性问题,另一种方法是简单地让{test}成为一个子程序本身,返回一个ref,然后你可以在你需要的时候得到它:

sub tests {
    return [ 
        $self->can('_sub1'),
        $self->can('_sub2')
    ];
}

然后使用它:

for $tn (0..$#{$self->tests()}) {
   ...
}

当然,如果你不得不迭代refs,你也可以直接传递引用:

for my $ref (0..$#{$self->tests()}) {
    $self->$ref(@args);
}

答案 1 :(得分:6)

虽然Robert P的答案可能对您有用,但它存在在此过程中尽早修复调度的问题。我倾向于尽可能晚地解决方法,所以我会将tests数组中的内容保留为方法名称,直到您想要使用它们为止:

 $self->{tests} = [
     qw( _sub1 _sub2 )
     ];

动态语言的优势在于,只要您愿意决定将要发生什么,您就可以等待。

当你想要运行它们时,你可以经历与罗伯特已经注意到的相同的过程。我会为它添加一个界面:

  foreach my $method_name ( $obj->get_test_methods )
      {
      $obj->$method_name();
      }

这可能会更好,因为没有将测试绑定到现有的方法名称:

  foreach my $method_name ( $obj->get_test_methods )
      {
      $obj->run_test_named( $method_name );
      }

run_test_named可以成为您的调度员,而且可以非常灵活:

 sub run_test_named
      {
      my( $self, $name ) = @_;

      # do anything you want, like in Robert's answer
      }

您可能想要做的一些事情:

  • 在对象上运行方法
  • 将对象作为参数传递给其他人
  • 暂时覆盖测试
  • 什么都不做
  • 等等

当您将自己决定执行的内容与其实施分开时,您可以获得更多自由。不仅如此,下次调用相同的测试名称时,您可以做一些不同的事情。

答案 2 :(得分:6)

use lib Alpha;

my $foo = Alpha::Foo->new; # indirect object syntax is deprecated

$foo->bar();

my %disp_table = ( bar => sub { $foo->bar() } );

$disp_table{bar}->(); # call it

你需要一个闭包,因为你想把一个方法调用变成一个普通的子程序调用,所以你必须捕获你正在调用该方法的对象。

答案 3 :(得分:3)

有几种方法可以做到这一点。你的第三种方法是最接近的这将存储对数组中两个subs的引用。然后,当你想要调用它们时,你必须确保将它们作为第一个参数传递给它们。

您是否有使用use fields构造的原因?

如果你想创建自包含的测试子,你可以这样做:

$$self{test} = [ 
     map {
         my $code = $self->can($_); # retrieve a reference to the method
         sub {                  # construct a closure that will call it
             unshift @_, $self; # while passing $self as the first arg
             goto &$code;   # goto jumps to the method, to keep 'caller' working
         }    
     } qw/_sub1 _sub2/                  
 ];

然后给他们打电话

for (@{ $$self{test} }) {
    eval {$_->(args for the test); 1} or die $@;
}