Perl中对象构造函数中命名参数的习惯用法

时间:2012-06-20 10:53:10

标签: perl oop parameters idioms

在Perl中,如果我想在对象构造函数中使用命名参数,如果我希望进行一些验证,我的代码似乎有点笨拙。

sub new {

   my $class = shift;
   my $self = {};

   my %args = @_;
   foreach my $argname (keys %args) {
     if    ($argname eq 'FOO') { $self->{$argname} = $args{$argname}; }
     elsif ($argname eq 'BAR') { $self->{$argname} = $args{$argname}; }
     elsif ($argname eq 'BAZ') { $self->{$argname} = $args{$argname}; }
     …
     else                      { die "illegal argument $argname\n"; }
   }

   bless $self;
   return $self;
}

首先,有一个临时哈希(%args)似乎有点笨拙。其次,整个if链似乎冗长乏味。

后者可以简化为

  if ('-FOO-BAR-BAZ-'=~m/-$argname-/) { $self->{$argname} = $args{$argname} }
  else { die "..."; }

但我想这可以改进。

如果我需要检查值,仍然需要if … elsif链?

我搜索了一下,但找不到更好的成语。有没有(除了使用某种Perl OO框架)

5 个答案:

答案 0 :(得分:8)

我发现自己不断编写不必要的代码来检查给定的参数。但后来我发现了Params::Validate。它易于使用,如果验证失败,它提供非常清晰和用户友好的错误消息。涵盖所有可能的参数组合及其错误消息是一项繁琐的工作。我更喜欢这种方式:

use Params::Validate qw/:all/;
sub new {
    my $pkg = shift;
    validate(
        @_, {
            foo => { type => SCALAR | ARRAYREF },
            bar => { type => SCALAR, optional => 1},
            baz => { type => ARRAYREF, default => ['value'] },
            quux => { isa => 'CGI' }
        }
    );

    return bless { @_ }, $pkg;
}

以后这段代码

MyApp::Something->new(
    foo => 123,
    bbr => 'typo',
    quux => CGI->new()
);

变为:

The following parameter was passed in the call to MyApp::Something::new but was not listed in the validation options: bbr
 at test.pl line 14.
    MyApp::Something::new(undef, 'foo', 123, 'bbr', 'typo', 'quux', 'CGI=HASH(0x7fd4fa1857e0)') called at test.pl line 27

答案 1 :(得分:2)

您可以使用smart matching

my @validkeys = qw(FOO BAR BAZ);
if ($argname ~~ @validkeys) {     # smart matching
    $self->{$argname} = $args{$argname};
} else { die ... } 

如果您不喜欢智能匹配操作员的默默无闻,您可以一起摆动正则表达式

my $rx = '^' . join("|", @validkeys) . '$';
if ($argname =~ /$rx/) { ...

答案 2 :(得分:1)

警告!未经测试的代码。

检查有效密钥。

die "invalid args" if grep { ! /^FOO|BAR|BAZ$/ } keys %args;

存储%args

$self->{$_} = $args{$_} foreach(keys %args);

答案 3 :(得分:1)

进行验证,您可以定义所有合法参数的哈希值,然后只测试密钥是否在其中

例如:

my %legal = ('FOO' => 1, 'BAR' => 1, 'BAZ' => 1);
my %args = @_;
foreach my $argname (keys %args) {
    if(exists $legal{$argname}) { $self->{$argname} = $args{$argname}; }
    else { die "illegal argument $argname\n"; }
}
关于笨拙的事情:那就是在perl中做到这一点 它可以有效地使用散列,并且散列文字是可读的

答案 4 :(得分:0)

为了完整性我正在添加这个答案(我自己的问题),描述我实际要做的事情,这是基于几个答案的元素。

sub new {
  my $package = shift;

  # 1. validate argument names
  my %args = @_;
  my $valid = '^FOO|BAR|BAZ$';
  for (keys %args) { die "invalid arg $_\n" unless /$valid/; }

  # 2. construct instance from arguments
  return bless { @_ };
}

虽然我还没有使用Params :: Validate,但我接受了Sebastian's answer

注意:

  • 我正在部署到具有Perl 5.8(真的)但不是Params :: Validate的服务器。我有理由还没有推动升级到5.10.x等。

  • 对于我的具体情况,上述内容在简洁性和可读性之间取得了良好的平衡。我可以在没有太多重构的情况下添加更多验证。

  • 这补偿了getter / setter或accessor样式方法的一个优点,即设置参数(编译器捕获参数名称中的拼写错误,因为它是方法名称),同时更简洁。

对于其他人,以上不适用,所以我接受了塞巴斯蒂安的答案,我觉得这是最好的答案(YMMV)。