通过调用包装对象创建属性默认值

Creating attribute defaults by calling a wrapped object

我有一个包含 InnerClass 对象作为属性的 WrapperClass 对象。 InnerClass 对象有一个权重属性。我的 WrapperClass 对象也有一个权重属性,我希望它的默认值是 InnerClass 对象的权重属性的值。

#!/usr/bin/perl
package InnerClass;
use Moose;

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

package WrapperClass;
use Moose;

has 'wrapped' => (
    is => 'rw',
    lazy => 1,
    default => sub {InnerClass->new(weight => 1)},
);

has 'weight' => (
    is => 'rw',
    default => sub {
        my $self = shift;
        $self->wrapped->weight()
    },
    lazy => 1,
);

上面的代码有效,但实际上 InnerClass 有很多属性,WrapperClass 需要为这些属性做同样的事情。理想情况下,我在编写 WrapperClass 时会做这样的事情:

use Moose;

has 'wrapped' => (
    is => 'rw',
);

my @getDefaultsFromWrappers
    = qw(weight height mass x y z label); # etc ...

foreach my $attr (@getDefaultsFromWrappers) {
    has $attr => (
        is => 'rw',
        default => sub {
            # Somehow tell the default which attribute
            # it needs to call from wrapped object?
            my $self = shift;
            $self->wrapped->???()
        },
        lazy => 1,
    );
}

但是,无法将参数传递给默认值或构建器来告诉它它正在构建哪个属性。我考虑过使用 caller 但这似乎是一个 hack。

有谁知道我如何完成这种属性声明样式,或者是否需要分别声明每个属性及其默认值?

您可以在问号所在的位置使用 $attr,因为当您声明属性时它仍在范围内。

foreach my $attr (@getDefaultsFromWrappers) {
    has $attr => (
        is      => 'rw',
        default => sub { shift->wrapped->$attr() },
        lazy    => 1,
    );
}

以下是一个可能的替代方案,如果您的属性声明不统一,您可能希望使用它:

has weight => (
    is      => 'rw',
    isa     => 'Num',
    default => _build_default_sub('weight'),
    lazy    => 1,
);

has label => (
    is      => 'rw',
    isa     => 'Str',
    default => _build_default_sub('label'),
    lazy    => 1,
);

sub _build_default_sub {
    my ($attr) = @_;
    return sub { shift->wrapped->$attr };
}

这可能通过内部对象中的方法委托和默认值来更好地处理。

有了这些,你举的例子可以更好的写成:

#!/usr/bin/perl

use strict;
use warnings;

package InnerClass;

use Moose;

has weight => (
    is => 'rw',
    default => 1,
);

package WrapperClass;

use Moose;

has wrapped => (
    is => 'rw',
    isa => 'InnerClass',
    lazy => 1,
    default => sub { InnerClass->new },
    handles => [ 'weight' ],
);

package main;

my $foo = WrapperClass->new;

print $foo->weight;

任何额外的默认值都将作为默认值添加到 InnerClass 中,并在 WrapperClass 中添加到包装的 'handles' 数组引用以指示应将其委托给该对象。

如果不希望将默认值应用于 InnerClass 的所有实例,则可以从那里删除默认值,指定所有必需的属性(以提供更好的错误检测),并在默认构造函数中指定所有属性.