即使子类覆盖该属性,也会使用 Moose 属性默认值

Moose attribute default used even though subclass overrides the attribute

我正在修改 Intermediate Perl 中介绍的 Moose。我有一个摘要 class Animal 和 属性 sound。默认行为应该是抱怨 sound 必须在 subclasses:

中定义
package Animal;
use namespace::autoclean;
use Moose;

has 'sound' => (
    is => 'ro',
    default => sub {
        confess shift, " needs to define sound!"
    }
);

1;

一个 subclass 除了定义 sound:

什么都不用做
package Horse;
use namespace::autoclean;
use Moose;

extends 'Animal';

sub sound { 'neigh' }

1;

但是用

测试这个
use strict;
use warnings;
use 5.010;
use Horse;

my $talking = Horse->new;
say "The horse says ", $talking->sound, '.';

结果

Horse=HASH(0x3029d30) needs to define sound!

如果我用

中的更简单的东西替换Animal中的匿名函数
has 'sound' => (
    is => 'ro',
    default => 'something generic',
);

一切正常。这是为什么?为什么即使我在 subclass?

中重写了默认函数,它仍会执行

这里有两件事在起作用:属性如何初始化以及访问器如何工作。

非惰性 ('eager') 属性在实例化 class 时初始化。这就是为什么你实际上可以离开

say "The horse says ", $talking->sound, '.';

并得到同样的错误。另一方面,如果使属性惰性化,错误就会消失。这让我们找到了真正的原因:属性、访问器和构建器之间的区别。

Animal 有一个属性,sound,它只是一个存储一些与 class 实例相关的数据的地方。因为 sound 被声明为 roAnimal 也有一个充当访问器的方法,容易混淆地称为 sound。如果您调用此访问器,它会查看属性的值并将其提供给您。

但是这个值独立于存取器而存在。访问器提供了一种获取值的方法,但值的实际存在取决于属性的构建器。在这种情况下,属性的构建器是匿名方法sub { confess shift, " needs to define sound!" },一旦属性需要有值,它就会得到运行。

事实上,如果您省略 is => 'ro',您将完全阻止 Moose 创建访问器,并且在构建时仍会弹出错误。因为那是你的 class 构建 sound 属性的时候。

属性何时需要其值取决于您是否已将其声明为惰性。渴望的属性在对象构建时被赋予它们的值。是否有访问器并不重要,创建对象时会调用构建器。在这种情况下,建造者就死了。

惰性属性在第一次需要时被赋予它们的值。默认访问器尝试获取属性的值,这会导致构建器触发,从而导致脚本终止。当您覆盖 sound 时,您将默认访问器替换为不调用构建器的访问器,因此不会再死掉。

这是否意味着您应该使 sound 属性惰性化?不,我不这么认为。有更好的机制可用,具体取决于您要断言的内容。如果您要断言必须定义 Animal->sound,则可以像这样使用 BUILD

package Animal;
use namespace::autoclean;
use Moose;

has 'sound' => (is => 'ro');

sub BUILD {
    my ($self) = @_;

    confess "$self needs to define sound!"
        unless defined $self->sound;
}

1;

在对象构造期间,每个父 classes 的 BUILD 方法都会被调用,这让它们可以对对象状态进行断言。

另一方面,如果您想断言子 class 必须覆盖 sound,最好根本不要使 sound 成为属性.相反,

package Animal;
use namespace::autoclean;
use Moose;

sub sound {    
    confess "Abstract method `sound` called!";
}

1;