Как реализовать таблицу диспетчеризации в модуле 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];
}