1

我正在阅读一本关于 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;

  1. 现在散列的预先声明有什么用(不是空散列)?为什么@_在列表的最后一部分提供?做什么的?

接下来是基于上述代码的语句:

当用作实例方法时,此 Horse 构造函数会忽略其调用者的现有属性。您可以创建第二个构造函数,设计为作为实例方法调用,如果设计得当,您可以使用调用对象中的值作为新构造函数的默认值:

我不明白那90%的陈述。

  1. 什么是实例方法?还是对象方法?你能举个例子吗?

我知道这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
}

再说一次,我不知道它做了什么。所以任何人都可以为我澄清这一点。

4

6 回答 6

5

现在散列的预先声明有什么用(不是空散列)?为什么@_ 在列表的最后一部分提供?做什么的?

这是一种非常聪明的方法,可以让您一次完成两件事:

  1. 允许您拥有构造函数的默认值

  2. 允许您使用已传递给构造函数调用的部分(或全部)默认值。


这是如何运作的?基于您需要了解的有关哈希的 3 件事:

  • 哈希可以被视为一个列表,通过展平为“key1”、“value1”、“key2”、“value2”......列表。如果您将哈希作为参数传递给子例程,则会发生这种情况:mySub(%hash1)

  • 可以通过反向过程将列表(具有偶数个元素)转换为散列。

  • 从列表构造的散列,其中多次遇到某个键,将仅具有该键一次,并且 - 重要的是 - 生成的散列中该键的值将是与该键关联的值中的最后一个实例.

换句话说,以下 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 个元素,“legs”和“3”(通过展平该哈希获得)。

    • 然后,您的新哈希 - 将被分配到$self- 将从以下列表中构建:

       ("color","bay", 
       "legs", "4", # Will get overwritten
       # more 
       "legs", "3")
      
    • 现在,根据上面的第三个项目符号,“legs”、“4”对在哈希分配中被后来的“legs”、“3”覆盖;因此,生成的哈希值将具有(默认)值“bay”作为颜色,以及通过构造函数参数传递的值为“3”作为腿。

于 2013-06-18T07:30:07.043 回答
3

0. 术语

  • 在 Perl OO 中,方法只是常规的子例程。调用方法的对象称为invocant,并作为第一个参数传递。调用者也可以是包名。

    $invocant->method(@args)
    # roughly equivalent to Class::method($invocant, @args)
    
  • 属性是我们对象的属性。并非每个对象都必须具有属性,但现在我们假设是这种情况。我们将属性建模为哈希。例如

    my $object = bless { x => 1.0, y => -12 } => 'Point';
    

    有两个属性xy

  • Perl 不会区别对待实例方法和类方法。实例方法是在对象上调用的,而类方法是在包名上调用的:

    Class->class_method;
    my $foo = Class->new; # another class method
    $foo->instance_method;
    

    但是,Perl 本身并不会阻止您做任何您喜欢的事情,例如$foo->class_method.

1.构造函数说明

构造函数被传递一个调用者和可选参数。调用者可以是一个对象,也可以是类名。如果它是一个对象,我们用ref内置函数获取对象的类(ref纯字符串的 是空字符串,它是一个假值)。我建议不要使用这种习惯用法,它会引发对基于原型的 OO 的错误期望,并为所有引用返回真值,而不仅仅是对象。

接下来,我们使用属性/属性/字段设置哈希。首先是默认值。然后,我们将@_视为一个散列,用于覆盖默认值。在哈希构造函数中,所有值都被计算,但只保留最后一个条目。例如

my %hash = ( x => 1, x => 2 );

$hash{x} == 2。在构造函数中,@_ = (legs => 8, owner => 'Odin')将覆盖legsandowner值。如果没有参数传递给构造函数,则不会覆盖任何值。

2. 解释那个奇怪的句子

当用作实例方法时,此 Horse 构造函数会忽略其调用者的现有属性。您可以创建第二个构造函数,设计为作为实例方法调用,如果设计得当,您可以使用调用对象中的值作为新构造函数的默认值

如果我们设置一匹马

my $sleipnir = Horse->new(legs => 8, owner => 'Odin');

并创造另一匹马

my $shadowfax = $sleipnir->new(owner => 'Gandalf');

我们可能会期望它$shadowfax也有八条腿。情况并非如此:调用者仅用于指示类,而不是提供默认值。也就是说,上面的语句等价于

my $shadowfax = Horse->new(owner => 'Gandalf');

如果我们想使用调用者来提供默认值,我们应该编写一个新方法,例如clone返回对象的(修改后的)副本。

于 2013-06-18T07:38:20.150 回答
3

什么是实例方法?还是对象方法?你能举个例子吗?

实例方法和对象方法是一回事。实例是一个对象的 Java 术语,Perl 更频繁地使用“对象”来代替,尽管它取决于书籍/文档。

在 Perl 中,有 3 种语法方式来调用包中的子例程:

  • 非OO(子程序)方法:

    mySub(@parameters)

    效果:在 sub 中,一个特殊的数组变量@_将包含(好吧,被别名,但让我们保持简单)@parameters,仅此而已。

  • OO 对象(或实例)调用:

    $obj->mySub(@parameters);

    现在,这假设它mySub是包中的一个方法,它$obj是一个创建的对象。

    效果:在 sub 中,一个特殊的数组变量@_将包含一个列表,该列表由将对象添加$obj到列表中@parameters

  • OO 包(或类,或有时称为静态)调用:

    MyClassName->mySub(@parameters);

    现在,这假定这mySub是 MyClassName 包中的一个方法。

    效果:在 sub 中,一个特殊的数组变量@_将包含一个列表,该列表由将字符串“MyClassName”(包名称)添加到列表中@parameters

    最后一个是构造函数的工作方式。

    • 当您调用Horse->new("legs"=>3);时,@_数组将包含sub:内的三个元素的列表。new()"Horse", "legs" and "3"

    • 当您的构造函数执行此操作my $invocant = shift;时,它会从其中删除字符串“Horse”@_并将其作为值分配给$invocant变量,让 @_ 包含原始参数列表“legs”和“3”。

您应该阅读perlobj文档以帮助您作为指南。

于 2013-06-18T07:42:29.580 回答
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;
} 
于 2013-06-18T08:42:44.170 回答
2

现在散列的预先声明有什么用(不是空散列)?为什么@_ 在列表的最后一部分提供?做什么的?

当您将列表分配给哈希时,该列表被视为键值对列表。如果某个键出现两次,则后面的值将覆盖前面的值。因此, 之前的键值@_是默认值,可以被传递给new( @_) 的参数覆盖。

再说一次,我不知道它做了什么。

它创建一个新对象,将调用者的值传递给构造函数以复制调用者。

于 2013-06-18T07:28:12.953 回答
0
my $class   = ref($invocant) || $invocant;

我不确定您正在阅读哪本书,但那条线正在鼓励不良做法。它正在创建一个可以在类或对象上调用的构造方法。

也就是说,您可以这样称呼它:

my $horse = Horse->new;

这样:

my $other_horse = $horse-new;

编写类方法或对象方法的方法是一种很好的 OO 实践。编写一种可以以两种方式调用的方法通常被认为是一个非常糟糕的主意。

于 2013-06-18T09:53:05.227 回答