面向对象的 Perl 构造函数语法和命名参数
Posted
技术标签:
【中文标题】面向对象的 Perl 构造函数语法和命名参数【英文标题】:Object-Oriented Perl constructor syntax and named parameters 【发布时间】:2010-12-14 18:05:42 【问题描述】:我对 Perl 构造函数中发生的事情有点困惑。我找到了这两个例子perldoc perlbot。
package Foo;
#In Perl, the constructor is just a subroutine called new.
sub new
#I don't get what this line does at all, but I always see it. Do I need it?
my $type = shift;
#I'm turning the array of inputs into a hash, called parameters.
my %params = @_;
#I'm making a new hash called $self to store my instance variables?
my $self = ;
#I'm adding two values to the instance variables called "High" and "Low".
#But I'm not sure how $params'High' has any meaning, since it was an
#array, and I turned it into a hash.
$self->'High' = $params'High';
$self->'Low' = $params'Low';
#Even though I read the page on [bless][2], I still don't get what it does.
bless $self, $type;
另一个例子是:
package Bar;
sub new
my $type = shift;
#I still don't see how I can just turn an array into a hash and expect things
#to work out for me.
my %params = @_;
my $self = [];
#Exactly where did params'Left' and params'Right' come from?
$self->[0] = $params'Left';
$self->[1] = $params'Right';
#and again with the bless.
bless $self, $type;
下面是使用这些对象的脚本:
package main;
$a = Foo->new( 'High' => 42, 'Low' => 11 );
print "High=$a->'High'\n";
print "Low=$a->'Low'\n";
$b = Bar->new( 'Left' => 78, 'Right' => 40 );
print "Left=$b->[0]\n";
print "Right=$b->[1]\n";
我已经将我一直遇到的问题/困惑作为 cmets 注入到代码中。
【问题讨论】:
这是基本的 Perl 将列表转换为哈希。它交替进行,将第一个作为键,第二个作为值。 DWIM(按照我的意思去做)是 Perl 努力实现的目标——而且通常会达到。 因为这个页面对于“命名参数”(以及其他东西)这个话题非常有用,所以我在标题中添加了这个短语,以便人们可以找到它。 【参考方案1】:.1。在 Perl 中,构造函数只是一个名为 new 的子例程。
是的,按照惯例,new
是一个构造函数。它也可能执行初始化或不执行初始化。 new
应该在成功时返回一个对象,或者在发生阻止创建对象的错误时抛出异常 (die
/croak
)。
您可以为您的构造函数命名任何您喜欢的名称,拥有任意数量的构造函数,甚至可以将 bless 对象构建到您想要的任何名称空间中(这并不是一个好主意)。
.2。我根本不明白my $type = shift;
做了什么,但我总是能看到。我需要吗?
shift
不带参数从@_
的头部取出一个参数并将其分配给$type
。 ->
运算符将调用者(左侧)作为第一个参数传递给子例程。所以这一行从参数列表中获取类名。而且,是的,你确实需要它。
.3。输入数组如何成为%params
哈希? my %params = @_;
分配到哈希是在列表上下文中完成的,列表项对被分组为键/值对。所以%foo = 1, 2, 3, 4;
,创建了一个散列,使得$foo1 == 2
和$foo3 == 4
。这通常用于为子例程创建命名参数。如果 sub 传递了奇数个参数,则启用警告时将生成警告。
.4。 'my $self = ;` 有什么作用?
此行创建一个匿名哈希引用并将其分配给词法范围变量$self
。哈希引用将存储对象的数据。通常,散列中的键与对象属性具有一对一的映射关系。因此,如果类 Foo 具有属性 'size' 和 'color',如果您检查 Foo 对象的内容,您会看到类似 $foo = size => 'm', color => 'black' ;
的内容。
.5。给定$self->'High' = $params'High';
$params'High'
来自哪里?
此代码依赖于传递给new
的参数。如果 new
被称为 Foo->new( High => 46 )
,那么根据问题 3 创建的散列将具有键 High
(46) 的值。在这种情况下,它相当于说$self->High = 46
。但是如果该方法被称为Foo->new()
,那么将没有可用的值,我们有$self->High = undef
。
.6。 bless
是做什么的?
bless
获取引用并与特定包关联,以便您可以使用它来进行方法调用。通过一个参数,引用与当前包相关联。使用两个参数,第二个参数指定要关联引用的包。最好始终使用两个参数形式,这样您的构造函数可以被子类继承并且仍然可以正常工作。
最后,我将重写基于散列的对象访问器,就像使用经典的 OO Perl 编写它一样。
package Foo;
use strict;
use warnings;
use Carp qw(croak);
sub new
my $class = shift;
croak "Illegal parameter list has odd number of values"
if @_ % 2;
my %params = @_;
my $self = ;
bless $self, $class;
# This could be abstracted out into a method call if you
# expect to need to override this check.
for my $required (qw name rank serial_number );
croak "Required parameter '$required' not passed to '$class' constructor"
unless exists $params$required;
# initialize all attributes by passing arguments to accessor methods.
for my $attrib ( keys %params )
croak "Invalid parameter '$attrib' passed to '$class' constructor"
unless $self->can( $attrib );
$self->$attrib( $params$attrib );
return $self;
【讨论】:
Re: #3,实际上散列的奇数个元素会导致警告,而不是致命错误。 你说如果 new 不能创建对象,它应该返回 undef。这不是一个硬性规定。有一个很好的论点说构造函数应该返回一个对象,或者如果不可能的话就死掉。这样任何调用构造函数的人都需要处理可能的异常。 @davorg,说得好。这就是即兴反应的问题所在。例外是正确的事情,这实际上是我一遍又一遍地编写代码要做的事情。适当记录和编辑。【参考方案2】:是的,我知道我在这里有点像死灵法师,但是......
虽然所有这些答案都很好,但我想我会提到Moose。 Moose 让构造函数变得简单(package Foo;use Moose;
自动提供了一个名为 new
的构造函数(尽管如果你愿意,可以重写名称“new”),但如果你需要它,doesn't take away any configurability。
一旦我浏览了 Moose 的文档(总体来说非常好,如果你适当地用谷歌搜索的话,还有更多关于 sn-ps 的教程),我再也没有回头。
【讨论】:
此答案中的第一个链接已损坏。能换吗?【参考方案3】:一些尚未处理的点:
在 Perl 中,构造函数只是一个 子程序称为
new
。
不完全是。调用构造函数 new 只是一个约定。你可以称它为任何你喜欢的名字。从 perl 的角度来看,这个名字并没有什么特别之处。
bless $self, $type;
您的两个示例都没有明确返回 bless 的结果。我希望你知道他们无论如何都是隐含地这样做的。
【讨论】:
是的,我知道在子例程中执行的最后一条语句就是返回的语句,如果那里没有明确的返回语句的话。 好。我只是想确定一下。 为了完成对以后访问者的澄清,构造函数是bless
es的子程序,并返回祝福引用。【参考方案4】:
在 Perl 中,子例程的所有参数都通过预定义数组 @_
传递。
shift
删除并返回 @_
数组中的第一项。在 Perl OO 中,这是方法调用者——通常是构造函数的类名和其他方法的对象。
散列扁平化并可以由列表初始化。模拟子程序的命名参数是一个常见的技巧。例如
Employee->new(name => 'Fred Flintstone', occupation => 'quarry worker');
忽略类名(已移开),奇数元素成为哈希键,偶数元素成为相应的值。
my $self =
创建一个新的散列reference 来保存实例数据。 bless
函数将普通哈希引用 $self
转换为对象。它所做的只是添加一些元数据,将引用标识为属于该类。
【讨论】:
【参考方案5】:您的问题与 OO Perl 无关。您对数据结构感到困惑。
可以使用列表或数组来初始化哈希:
my @x = ('High' => 42, 'Low' => 11);
my %h = @x;
use Data::Dumper;
print Dumper \%h;
$VAR1 =
'低' => 11,
'高' => 42
;
当您在 bless
ed 引用上调用方法时,该引用将添加到方法接收的参数列表之前:
#!/usr/bin/perl
package My::Mod;
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 0;
sub new bless [] => shift
sub frobnicate Dumper(\@_)
package main;
use strict;
use warnings;
my $x = My::Mod->new;
# invoke instance method
print $x->frobnicate('High' => 42, 'Low' => 11);
# invoke class method
print My::Mod->frobnicate('High' => 42, 'Low' => 11);
# call sub frobnicate in package My::Mod
print My::Mod::frobnicate('High' => 42, 'Low' => 11);
输出:
$VAR1 = [bless([], 'My::Mod'),'High',42,'Low',11]; $VAR1 = ['My::Mod','High',42,'Low',11]; $VAR1 = ['高',42,'低',11];【讨论】:
【参考方案6】:要回答您问题的主要内容,由于哈希可以初始化为 key => value
对的列表,您可以将这样的列表发送给函数,然后将 @_
分配给哈希。这是在 Perl 中执行命名参数的标准方式。
例如,
sub foo
my %stuff = @_;
...
foo( beer => 'good', vodka => 'great' );
这将导致子例程 foo
中的 %stuff
具有包含两个键 beer
和 vodka
以及相应值的散列。
现在,在 OO Perl 中,还有一些额外的问题。每当您使用箭头 (->
) 运算符调用方法时,箭头左侧的任何内容都会卡在 @_
数组的开头。
所以如果你说Foo->new( 1, 2, 3 )
;
然后在您的构造函数中,@_
将如下所示:( 'Foo', 1, 2, 3 )
。
所以我们使用shift
,它不带参数隐式地对@_
进行操作,从@_
中取出第一项,并将其分配给$type
。在那之后,@_
只剩下我们的名称/值对了,为了方便,我们可以直接将其分配给哈希。
然后我们将$type
值用于bless
。 bless
所做的只是获取一个引用(在您的第一个示例中是一个哈希引用)并说“这个引用与特定的包相关联”。 Alakazzam,你有一个对象。
请记住,$type
包含字符串 'Foo',它是我们包的名称。如果您不为bless
指定第二个参数,它将使用当前包的名称,这也适用于本示例,但不适用于继承的构造函数。
【讨论】:
你可以使用my($self,%stuff) = @_;
而不是换档。【参考方案7】:
如果将数组分配给哈希,perl 将数组中的交替元素视为键和值。你的数组看起来像
my @array = (key1, val1, key2, val2, key3, val3, ...);
当您将其分配给 %hash 时,您会得到
my %hash = @array;
# %hash = ( key1 => val1, key2 => val2, key3 => val3, ...);
这是另一种说法,在 perl 列表/哈希构造语法中,","
和 "=>"
意思相同。
【讨论】:
+1 但请注意,如果 LHS 仅由 [A-Za-z0-9_] 组成,则粗逗号 (=>
) 会自动引用 LHS。以上是关于面向对象的 Perl 构造函数语法和命名参数的主要内容,如果未能解决你的问题,请参考以下文章