Есть ли способ заменить if-elsif-else в Perl чем-то лучшим?
Я хочу построить кучу субротинов Perl, которые имеют один и тот же шаблон if elsif elsif else
это принимает решение на основе факторной переменной. Вот пример шаблона подпрограммы:
sub get_age{
my $factor=shift;
if ($factor == 1 ){ print "do something" }
elsif ($factor == 2 ){ print "do somthing2" }
elsif ($factor == 3 ){ print "do somthing3" }
elsif ($factor == 4 ){ print "do somthing4" }
else { print "error" }
}
мне интересно, есть ли какой-то шаблон дизайна на Perl для замены if else
условие с более элегантным решением, которое легко поддерживать в будущем, особенно если мне нужно изменить некоторые условия или удалить некоторые из них?
6 ответов
несколько человек упомянули таблицу отправки. Есть две вещи, и иногда приятно держать их порознь. Есть список возможных вещей, которые могут произойти, и то, что заставляет их происходить. Если вы соедините два, вы застряли с вашим решением. Если вы будете держать их отдельно, у вас будет больше гибкости позже.
таблица отправки задает поведение как данные вместо структуры программы. Вот два разных способа сделать это. С вас пример целые числа и что-то в этом роде могут использовать массив для хранения вещей. Пример хэша-та же идея, но смотрит на поведение немного по-другому.
также обратите внимание, что я учитываю print
. Когда вы повторили такой код, попробуйте переместить повторяющийся материал на уровень выше.
use v5.10;
foreach my $factor ( map { int rand 5 } 0 .. 9 ) {
say get_age_array( $factor );
}
my @animals = qw( cat dog bird frog );
foreach my $factor ( map { $animals[ rand @animals ] } 0 .. 9 ) {
say get_age_hash( $factor );
}
sub get_age_array {
my $factor = shift;
state $dispatch = [
sub { 'Nothing!' }, # index 0
sub { "Calling 1" },
sub { 1 + 1 },
sub { "Called 3" },
sub { time },
];
return unless int $factor <= $#$dispatch;
$dispatch->[$factor]->();
}
sub get_age_hash {
my $factor = shift;
state $dispatch = {
'cat' => sub { "Called cat" },
'dog' => sub { "Calling 1" },
'bird' => sub { "Calling 2, with extra" },
};
return unless exists $dispatch->{$factor};
$dispatch->{$factor}->();
}
обновление: убедитесь, что вы прочитали комментарий Брайана ниже; в основном, лучше использовать for
вместо given
, из-за различных проблем он комментирует в своей ссылке. Я обновил свой совет, чтобы включить его улучшения, которые он описывает в использовать для() вместо():
если вы находитесь на perl 5.10 или новее,given/when
- это волшебная пара, которую вы ищете, но вы действительно должны использовать .. Вот пример:
use strict;
use warnings;
use feature qw(switch say);
print 'Enter your grade: ';
chomp( my $grade = <> );
for ($grade) {
when ('A') { say 'Well done!' }
when ('B') { say 'Try harder!' }
when ('C') { say 'You need help!!!' }
default { say 'You are just making it up!' }
}
просто делая вещи короче:
sub get_age1 {
my $age = shift;
$age == 1 ? print "do something" :
$age == 2 ? print "do somthing2" :
$age == 3 ? print "do somthing3" :
$age == 4 ? print "do somthing4" :
print "error"
}
это имеет больше смысла, если условие может быть лучше всего выражено как регулярное выражение:
sub get_age2 {
for (shift) {
if (/^ 1 $/x) {print "do something"}
elsif (/^ 2 $/x) {print "do somthing2"}
elsif (/^ 3 $/x) {print "do somthing3"}
elsif (/^ 4 $/x) {print "do somthing4"}
else {print "error" }
}
}
вот несколько таблиц отправки:
простой (с ошибкой):
{
my %age = ( # defined at runtime
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
);
# unsafe to call get_age3() before sub definition
sub get_age3 {
($age{$_[0]} or sub {print "error"})->()
}
}
лучше:
{
my %age;
BEGIN {
%age = ( # defined at compile time
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
)
}
# safe to call get_age4() before sub definition
sub get_age4 {
($age{$_[0]} or sub {print "error"})->()
}
}
другой способ написать это:
BEGIN {
my %age = ( # defined at compile time
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
);
# safe to call get_age5() before sub definition
sub get_age5 {
($age{$_[0]} or sub {print "error"})->()
}
}
еще один хороший способ, чтобы написать это:
{
my $age;
# safe to call get_age6() before sub definition
sub get_age6 {
$age ||= { # defined once when first called
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
};
($$age{$_[0]} or sub {print "error"})->()
}
}
таблицы отправки идеальная пригонка для этого типа картины дизайна. Я использовал эту идиому много раз. Что-то вроде этого:--3-->
sub get_age {
my $facter = shift;
my %lookup_map = (
1 => sub {.....},
2 => sub {.....},
3 => \&some_other_sub,
default => \&some_default_sub,
);
my $code_ref = $lookup_map{$facter} || $lookup_map{default};
my $return_value = $code_ref->();
return $return_value;
}
это работает, когда аргумент, который вы используете для определения того, какой случай выполняется, будет существовать как ключ в вашей хэш-таблице. Если возможно, что это не будет точное совпадение, вам может потребоваться использовать регулярные выражения или какой-либо другой способ соответствия вашего ввода тому биту кода для выполнения. Вы можете использовать regexes как хэш-ключи, такие как это:
my %patterns = (
qr{^/this/one}i => sub {....},
qr{^/that/one}is => sub {....},
qr{some-other-match/\d+}i => \&some_other_match,
)
my $code_ref;
for my $regex (keys %patterns) {
if ($facter =~ $regex) {
$code_ref = $patterns{$regex};
last;
}
}
$code_ref ||= \&default_code_ref;
$code_ref->();
Это может быть место для чего-то вроде диспетчерской стол. Я сам этого не делал, но эта страница может быть началом:http://www.perlmonks.org/?node_id=456530
использовать переключатель;
читать Отправка Таблиц на Высший Порядок Perl.