即使子类覆盖该属性,也会使用 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
被声明为 ro
,Animal
也有一个充当访问器的方法,容易混淆地称为 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;
我正在修改 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
被声明为 ro
,Animal
也有一个充当访问器的方法,容易混淆地称为 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;