Как реализовать таблицу диспетчеризации в модуле Perl OO?

Я хочу поместить некоторые подсайты, которые находятся внутри пакета OO, в массив - также внутри пакета - для использования в качестве таблицы отправки. Что-то вроде этого

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.

Любая помощь очень ценится, Иэн

Ответ 1

Хотя ответ 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 answer
      }

Некоторые вещи, которые вы, возможно, захотите сделать:

  • Выполнить метод для объекта
  • Передайте объект в качестве аргумента для чего-то еще
  • Временно переопределить тест
  • Ничего не делать
  • и т.д.

Когда вы отделяете то, что вы решаете от его реализации, у вас гораздо больше свободы. Не только это, в следующий раз, когда вы вызываете одно и то же имя теста, вы можете сделать что-то другое.

Ответ 2

Ваш друг 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);

Или, благодаря этому вопросу (который, случается, является вариантом этого вопроса), вы может вызвать его напрямую:

$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()}) {
   ...
}

Конечно, если вам все равно нужно перебирать ссылки, вы можете просто пойти прямо для передачи справки:

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

Ответ 3

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

Вам нужно закрыть, потому что вы хотите превратить вызов метода в обычный вызов подпрограммы, поэтому вам нужно захватить объект, на который вы вызываете метод.

Ответ 4

Есть несколько способов сделать это. Ваш третий подход ближе всего. Это сохранит ссылку на два подмножества в массиве. Затем, когда вы хотите их вызвать, вы должны обязательно передать им объект в качестве своего первого аргумента.

Есть ли причина, по которой вы используете конструкцию 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 [email protected];
}