我正在修补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',
);
事情很好。这是为什么?为什么即使我在子类中覆盖它也会执行默认函数?
答案 0 :(得分:3)
这里有两件事:初始化属性以及访问者如何工作。
在实例化类时初始化非惰性(' eager')属性。这就是为什么你真的可以放弃
的原因say "The horse says ", $talking->sound, '.';
并得到同样的错误。另一方面,如果使属性变为惰性,则错误消失。这导致了我们真正的原因:属性,访问者和构建者之间的差异。
Animal
有一个属性sound
,它只是一个存储与该类实例相关的数据的地方。由于sound
被声明为ro
,Animal
也有一个充当访问者的方法,令人困惑地也称为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;