即使子类重写属性,也会使用Moose属性默认值

时间:2015-07-04 03:08:56

标签: perl oop moose

我正在修补Intermediate Perl中介绍的Moose。我有一个带有属性Animal的抽象类sound。默认行为应该是抱怨必须在子类中定义sound

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

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

1;

除了定义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',
);
事情很好。这是为什么?为什么即使我在子类中覆盖它也会执行默认函数?

1 个答案:

答案 0 :(得分:3)

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

在实例化类时初始化非惰性(' eager')属性。这就是为什么你真的可以放弃

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

并得到同样的错误。另一方面,如果使属性变为惰性,则错误消失。这导致了我们真正的原因:属性,访问者和构建者之间的差异。

Animal有一个属性sound,它只是一个存储与该类实例相关的数据的地方。由于sound被声明为roAnimal也有一个充当访问者的方法,令人困惑地也称为sound。如果您调用此访问器,它会查看该属性的值并将其提供给您。

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

事实上,如果你遗漏is => 'ro',你将阻止Moose创建一个访问者,错误仍然会在施工时弹出。因为您的班级构建了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;

在对象构建期间,每个父类'调用BUILD个方法,让它们对对象状态进行断言。

另一方面,如果你想断言的是子类必须覆盖sound,那么最好不要让sound成为属性。取而代之的是,

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

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

1;