我正在读一本关于perl的书,到目前为止我理解了OOP的概念,直到我遇到这段代码:
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {
color => "bay",
legs => 4,
owner => undef,
@_, # Override previous attributes
};
return bless $self, $class;
}
$ed = Horse->new; # A 4-legged bay horse
$stallion = Horse->new(color => "black"); # A 4-legged black horse
我在该代码中看到的是,在new
子例程中传递的任何内容都被视为包名称,该名称将使用以下代码转换为对象引用:
my $invocant = shift; #this one just get the name of the package which is the argument passed
return bless $self, $class;
@_
?做什么的? 接下来是基于上述代码的声明:
当用作实例方法时,此Horse构造函数会忽略其调用者的现有属性。您可以创建第二个被设计为实例方法的构造函数,如果设计得当,您可以使用调用对象中的值作为新函数的默认值:
我不理解该声明的90%。
我知道这个my $class = ref($invocant) || $invocant;
是对象和实例方法,但我不确定它们有何不同或如何以不同方式使用它们。
"第二个构造函数"上面提到的是:
$steed = Horse->new(color => "dun");
$foal = $steed->clone(owner => "EquuGen Guild, Ltd.");
sub clone {
my $model = shift;
my $self = $model->new(%$model, @_);
return $self; # Previously blessed by ->new
}
再次,我不知道它做了什么。所以任何人都可以为我澄清这一点。
答案 0 :(得分:5)
现在哈希的预先声明有什么用(不是空哈希)?为什么在列表的最后部分提供@_?做什么的?
这是一种非常聪明的方法,可以让您一次完成两件事:
允许您拥有构造函数的默认值
允许您使用传入构造函数调用的方式覆盖某些(或全部)默认值。
这是如何工作的?基于哈希需要了解的3件事:
通过展平为“key1”,“value1”,“key2”,“value2”...列表,可以将哈希视为列表。如果将散列作为参数传递给子例程,则会发生这种情况:mySub(%hash1)
可以通过反向过程将列表(甚至包含元素数量)转换为哈希值。
从列表构造的哈希,其中多次遇到某个键,只有一次该键,并且 - 这里很重要 - 结果哈希中该键的值将是LAST实例与该密钥相关的值之间。
换句话说,以下4个赋值产生相同的精确结果数据结构:
my %hash1;
$hash1{20} = 2;
$hash1{40} = 4;
my %hash2 = ( 20, 2, 40, 4); # Notice that "," is same as "=>"
my %hash3 = ( 20 => 2, 40 => 4); # Notice that "," is same as "=>"
my %hash4 = ( 40 => 1, 40 => 3, 20 => 2, 40 => 4);
# first 2 couples will be effectively ignored, due to same keys later on
示例:
如果您传入的哈希值不是color
,而是腿:Horse->new(legs=>3)
@_
数组将包含2个元素,“leg”和“3”(通过展平该哈希获得)。
您的新哈希 - 将分配给$self
- 然后将从以下列表构建:
("color","bay",
"legs", "4", # Will get overwritten
# more
"legs", "3")
现在,根据上面的第三个子弹,“leg”,“4”对被后来的“leg”,“3”覆盖在哈希分配中;所以得到的散列的颜色为(默认)值为“bay”,腿的传递值为“3”。
答案 1 :(得分:3)
在Perl OO中,方法只是常规子例程。调用方法的对象称为 invocant ,并作为第一个参数传递。调用者也可以是包名。
$invocant->method(@args)
# roughly equivalent to Class::method($invocant, @args)
属性是我们对象的属性。并非每个对象都必须具有属性,但是现在我们假装是这种情况。我们将属性建模为哈希。 E.g。
my $object = bless { x => 1.0, y => -12 } => 'Point';
有两个属性x
和y
。
Perl不会以不同方式处理实例方法和类方法。 实例方法意味着在对象上调用,而类方法意味着在包名称上调用:
Class->class_method;
my $foo = Class->new; # another class method
$foo->instance_method;
但是,Perl本身并不妨碍你做任何你喜欢的事情,例如$foo->class_method
。
构造函数传递一个invocant和可选参数。调用者可以是对象,也可以是类名。如果它是一个对象,我们获得内置ref
的对象的类(普通字符串的ref
是空字符串,这是一个假值)。我建议反对这个成语,它提出了基于原型的OO的错误期望,并返回所有引用的真值,而不仅仅是对象。
接下来,我们设置一个带属性/属性/字段的哈希。首先是默认值。然后,我们将@_
视为散列,用于覆盖默认值。在哈希构造函数中,将评估所有值,但仅保留最后一个条目。 E.g。
my %hash = ( x => 1, x => 2 );
有$hash{x} == 2
。在构造函数中,@_ = (legs => 8, owner => 'Odin')
会覆盖legs
和owner
值。如果没有参数传递给构造函数,则不会覆盖任何值。
当用作实例方法时,此Horse构造函数会忽略其调用者的现有属性。您可以创建第二个被设计为实例方法的构造函数,如果设计得当,您可以使用调用对象中的值作为新函数的默认值
如果我们建立像
这样的马my $sleipnir = Horse->new(legs => 8, owner => 'Odin');
并创造另一匹马
my $shadowfax = $sleipnir->new(owner => 'Gandalf');
我们可能期望$shadowfax
也有八条腿。情况并非如此:调用者仅用于指示类,而不是用于提供默认值。也就是说,上面的陈述相当于
my $shadowfax = Horse->new(owner => 'Gandalf');
如果我们想使用一个调用来提供默认值,我们应该编写一个新方法,例如clone
返回对象的(修改过的)副本。
答案 2 :(得分:3)
什么是实例方法?或对象方法?你能提供一个例子吗?
实例和对象方法是一回事。实例是一个对象的Java术语,Perl更频繁地使用" object"相反,虽然它取决于书籍/文件。
在Perl中,有三种语法方法可以调用包中的子例程:
非OO(子程序)方法:
mySub(@parameters)
效果:在sub中,一个特殊的数组变量@_
将包含(好吧,别名,但让我们保持简单)@parameters
并没有别的。
OO对象(或实例)调用:
$obj->mySub(@parameters);
现在,假设mySub
是包中的方法,其中$obj
是创建的对象。
效果:在sub中,一个特殊的数组变量@_
将包含一个由将对象$obj
添加到列表@parameters
之前的列表。
OO Package(或类,有时称为静态)调用:
MyClassName->mySub(@parameters);
现在,假设mySub
是MyClassName包中的方法。
效果:在sub中,一个特殊的数组变量@_
将包含一个由前置字符串" MyClassName"组成的列表。 - 包名称到列表@parameters
。
最后一个是构造函数的工作方式。
当您致电Horse->new("legs"=>3);
时,@_
数组将在new()
子资料中包含三个元素列表:"Horse", "legs" and "3"
当您的构造函数执行my $invocant = shift;
时,它删除了字符串" Horse"来自@_
并将其指定为$invocant
变量的值,让@_包含原始参数列表," leg"和" 3"。
您应阅读perlobj文档,以帮助您作为指南。
答案 3 :(得分:3)
已经解释了@_
行正在做什么,这里有一个稍微容易理解的版本(不是答案,只是添加到讨论中 - 除了需要代码格式化之外还会发布评论):
sub new {
my ($invocant,%args) = @_;
my $class = ref($invocant) || $invocant;
my $self = {
color => "bay",
legs => 4,
owner => undef,
%args, # Override previous attributes
};
return bless $self, $class;
}
答案 4 :(得分:2)
现在哈希的预先声明有什么用(不是空哈希)?为什么在列表的最后部分提供@_?做什么的?
将列表分配给哈希时,列表将被视为键值对列表。如果某个键出现两次,则后一个值将覆盖之前的值。因此,@_
之前的键值是默认值,可以通过传递给new
(@_
)的参数覆盖。
再一次,我不知道它做了什么。
它创建一个新对象,将调用者的值传递给构造函数,以便复制调用者。
答案 5 :(得分:0)
my $class = ref($invocant) || $invocant;
我不确定你正在阅读哪本书,但这条路线鼓励不良做法。它正在创建一个可以在类或对象上调用的构造函数方法。
也就是说,您可以这样称呼它:
my $horse = Horse->new;
就这样:
my $other_horse = $horse-new;
编写 类方法或对象方法的方法是一种很好的OO实践。编写一种可以通过两种方式调用的方法通常被认为是非常糟糕的主意。