写入 Perl Moose 中的只读属性 class

Writing to read-only attributes inside a Perl Moose class

使用 Perl 和 Moose,可以通过两种方式访问​​对象数据。

$self->{attribute}$self->attribute()

这里有一个简单的例子来展示两者:

# Person.pm
package Person;

use strict;
use warnings;
use Moose;

has 'name' => (is => 'rw', isa => 'Str');
has 'age'  => (is => 'ro', isa => 'Int');

sub HAPPY_BIRTHDAY {
    my $self = shift;
    $self->{age}++;   # Age is accessed through method 1
}

sub HAPPY_BIRTHDAY2 {
    my $self = shift;
    my $age = $self->age();
    $self->age($age + 1);   # Age is accessed through method 2 (this will fail)
}

1;

# test.pl
#!/usr/bin/perl

use strict;
use warnings;
use Person;

my $person = Person->new(
    name => 'Joe',
    age  => 23,
);

print $person->age()."\n";

$person->HAPPY_BIRTHDAY();
print $person->age()."\n";

$person->HAPPY_BIRTHDAY2();
print $person->age()."\n";

我知道当你在 Person.pm 文件之外时,最好使用 $person->age() 版本,因为它可以防止你犯愚蠢的错误并阻止你覆盖只读值, 但我的问题是...

Inside of Person.pm is it best to use $self->{age} or $self->age()? Is it considered bad practice to overwrite a read-only attribute within the module itself?

Should this attribute be changed to a read/write attribute if its value is ever expected to change, or is it considered acceptable to override the read-only aspect of the attribute by using $self->{age} within the HAPPY_BIRTHDAY function?

我不认为 $self->{age} 是一个文档化的接口,所以它甚至不能保证工作。

在这种情况下,我会使用 https://metacpan.org/pod/Moose::Manual::Attributes#Accessor-methods 中所述的私人编写器:

has 'weight' => (
    is     => 'ro',
    writer => '_set_weight',
);

您甚至可以使用 https://metacpan.org/pod/MooseX::AttributeShortcuts#is-rwp 中的 'rwp' 自动执行此操作:

use MooseX::AttributeShortcuts;

has 'weight' => (
    is => 'rwp',
);

使用 Moose 时,最佳做法是始终使用生成的访问器方法,即使在对象自己的方法中也是如此 class。以下是几个原因:

  1. 访问器方法可能会被执行特殊操作的子 class 覆盖。调用 $self->age() 确保调用正确的方法。

  2. 可能有方法修饰符,例如 beforeafter,附加到属性。直接访问哈希值将跳过这些。

  3. 可能有一个谓词或更清晰的方法附加到属性(例如 has_age)。直接乱用哈希值会让他们迷惑。

  4. 散列键容易出现拼写错误。如果你不小心说了 $self->{aeg} 错误将不会被立即捕获。但是 $self->aeg 会死掉,因为这个方法不存在。

  5. 一致性好。没有理由在一个地方使用一种风格而在其他地方使用另一种风格。它也使新手更容易理解代码。

在只读属性的特定情况下,这里有一些策略:

  1. 使您的对象真正不可变。如果您需要更改一个值,请构造一个新对象,该对象是具有新值的旧对象的克隆。

  2. 使用只读属性存储真实年龄,指定私有写法

例如:

package Person;
use Moose;

has age => ( is => 'ro', isa => 'Int', writer => '_set_age' );

sub HAPPY_BIRTHDAY {
    my $self = shift;
    $self->_set_age( $self->age + 1 );
}

更新

下面是一个示例,说明您可以如何使用惰性构建器根据一个属性设置另一个属性。

package Person;
use Moose;

has age     => ( is => 'rw', isa => 'Int', lazy => 1, builder => '_build_age' );
has is_baby => ( is => 'rw', isa => 'Bool', required => 1 );

sub _build_age { 
    my $self = shift;
    return $self->is_baby ? 1 : 52
}

直到访问 age 才会调用惰性构建器,因此您可以确定 is_baby 会在那里。

直接设置hash元素当然会跳过builder方法

开箱即用的 perl 不是类型安全的,也没有太多封装方式,因此很容易做出鲁莽的事情。 Moose 在您的 perl 对象上强加了一些文明,用安全和稳定换取了一些自由。如果 Moose 变得太令人窒息,底层的 Perl 仍然存在,所以有办法绕过 Moose 的铁拳试图制定的任何法律。

一旦您意识到您已将属性声明为只读,但您想要更改它,即使您也说过您希望它是只读的,并且在大多数宇宙中您将某些内容声明为只读,因为您不想更改它,然后一定要继续更新 $person->{age}。毕竟,你知道自己在做什么。