【问题标题】:Is there a way to replace an if-elsif-else in Perl with something better?有没有办法用更好的东西替换 Perl 中的 if-elsif-else?
【发布时间】:2012-01-09 05:50:48
【问题描述】:

我想构建一堆 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 条件,特别是如果我需要更改某些条件或删除其中一些条件,那么将来易于维护?

【问题讨论】:

  • 始终包含实际运行的代码。

标签: perl design-patterns perl-module perl-data-structures


【解决方案1】:

有几个人提到了调度表。有两件事,有时将它们分开很好。有可能发生的事情的清单,以及使它们发生的事情。如果你将两者结合起来,你就会被困在你的解决方案中。如果你把它们分开,你以后会有更大的灵活性。

调度表将行为指定为数据而不是程序结构。这是两种不同的方法。在您的示例中,您有整数,类似的东西可能会使用数组来存储东西。哈希示例是相同的想法,但查找行为略有不同。

还请注意,我排除了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}->();   
    }

【讨论】:

  • 我在发布我的帖子后注意到了你的回答,同样的想法,正确的实现;-)
【解决方案2】:

更新:请务必阅读下面的 brian 评论;基本上,由于他在链接中遇到的各种问题,最好使用for 而不是given。我更新了我的建议以纳入他的改进,他在Use for() instead of given() 中概述了这些改进:

如果您使用的是 perl 5.10 或更高版本,given/when 是您正在寻找的魔法对,但您确实应该使用 for/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!' }
}

【讨论】:

【解决方案3】:

只是让事情变得更短:

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"})->()
    }
}

【讨论】:

  • 在第一个示例中,由于您在任何情况下都在打印,您可以使用单个 print 并在字符串上应用条件:print ( $age == 1 ? ... : $age == 2 ? ... : ... );
【解决方案4】:

调度表非常适合这种类型的设计模式。这个成语我用过很多次了。像这样的:

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;
}

当您用于确定执行哪个案例的参数将作为哈希表中的键存在时,此方法有效。如果它可能不是完全匹配,那么您可能需要使用正则表达式或其他方式将您的输入与要执行的代码相匹配。您可以像这样使用正则表达式作为哈希键:

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->();

【讨论】:

  • 您还可以在每次调用 get_age 时重新定义调度表。 :)
  • 哈希的键总是字符串。所以,我不确定qr// 的意义是什么。
  • @briandfoy,是的,你是对的。调度表很可能不会从一次调用 get_age() 更改为另一次调用。因此,最好将该调度表从 sub 中拉出,以便只定义一次。
  • @localfilmmaker 请参阅perldoc.perl.org/perldata.html 哈希是标量值的无序集合,由其关联的 string 键索引。(强调我的)。
  • 感谢@SinanÜnür 和 briandfoy 的澄清。
【解决方案5】:

参见示例/参考/dispatch_table.pl

https://code-maven.com/slides/perl/dispatch-table

#!/usr/bin/perl
use strict;
use warnings;

# Use subroutine references in a hash to define what to do for each case

my %dispatch_table = (
    '+' => \&add,
    '*' => \&multiply,
    '3' => \&do_something_3,
    '4' => \&do_something_4,
);

foreach my $operation ('+', 'blabla', 'foobar', '*'){
    $dispatch_table{$operation}->(
        var1 => 5,
        var2 => 7,
        var3 => 9,                       
    ) if ( exists $dispatch_table{$operation} );
}

sub add {
    my %args = (@_);
    my $var1 = $args{var1}; 
    my $var2 = $args{var2};

    my $sum = $var1 + $var2;
    print "sum = $sum \n";
    return;
}

sub multiply {
    my %args = (@_);
    my $var1 = $args{var1}; 
    my $var3 = $args{var3};

    my $mult = $var1 * $var3;
    print "mult = $mult \n";
    return;
}

输出:

sum = 12 
mult = 45 

【讨论】:

    【解决方案6】:

    这可能是一个类似调度表的地方。我自己没有做过,但这个页面可能是一个开始:http://www.perlmonks.org/?node_id=456530

    【讨论】:

      【解决方案7】:

      使用Switch

      Higher Order Perl中读取Dispatch Tables

      【讨论】:

      • Switch 在 v5.14 中已从 Perl 核心中移除,因此您不应将其用于新代码。
      猜你喜欢
      • 2013-04-29
      • 2021-12-29
      • 2016-06-17
      • 2016-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-09
      相关资源
      最近更新 更多