【问题标题】:Moose attribute default used even though subclass overrides the attributeMoose 属性默认使用,即使子类覆盖该属性
【发布时间】:2023-03-20 01:28:01
【问题描述】:

我正在修改 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',
);

一切正常。这是为什么?为什么我在子类中重写了默认函数?

【问题讨论】:

    标签: perl oop moose


    【解决方案1】:

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

    非惰性('eager')属性在类被实例化时被初始化。这就是为什么你实际上可以省略

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

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

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

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

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

    属性何时需要它的值取决于您是否将其声明为惰性。急切的属性在对象构造中被赋予了它们的值。是否有访问器无关紧要,创建对象时会调用构建器。在这种情况下,builder 会死掉。

    惰性属性在第一次被需要时就被赋予了它们的值。默认访问器尝试获取属性的值,这会导致构建器触发,从而导致脚本终止。当您覆盖 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;
    

    【讨论】:

    • 很好,但是您的段落以 “但这个值独立于访问者而存在” 开头可能会更清楚。我认为 为什么 删除 readonly 属性会摆脱访问器并不明显,而且我认为 OP 并不清楚他指定了一个构建器
    • 感谢您的详尽解释。我认为这个例子过于简单,并且人为地需要一种或另一种技术,我猜这本书只是想展示不同的选择。我已经开始研究 the documentation 的建设者、默认设置等,您的回答绝对是一个很好的起点。
    猜你喜欢
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-02
    • 1970-01-01
    • 1970-01-01
    • 2019-07-26
    • 2013-08-03
    相关资源
    最近更新 更多