【问题标题】:How can I build multiple attributes with a single builder in Moose?如何在 Moose 中使用单个构建器构建多个属性?
【发布时间】:2017-08-09 16:47:21
【问题描述】:

使用 Moose,是否可以创建一个同时构建多个属性的构建器?

我有一个项目,其中对象有多个“组”字段 - 如果请求该组的任何成员,我想继续填充它们。我的假设是,如果我需要姓名,我还需要生日,并且由于它们在同一个表中,因此在一个查询中获取两者会更快。

我不确定我的问题是否足够清楚,但希望一些示例代码可以说明问题。

我有什么:

Package WidgetPerson;
use Moose;

has id => (is => 'ro', isa => 'Int' );
has name => (is => 'ro', lazy => 1, builder => '_build_name');
has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate');
has address => (is => 'ro', lazy => 1, builder => '_build_address');

sub _build_name {
 my $self = shift;
 my ($name) = $dbh->selectrow_array("SELECT name FROM people WHERE id = ?", {}, $self->id);
 return $name;
}
sub _build_birthdate {
 my $self = shift;
 my ($date) = $dbh->selectrow_array("SELECT birthdate FROM people WHERE id = ?", {}, $self->id);
 return $date;
}
sub _build_address {
 my $self = shift;
 my ($date) = $dbh->selectrow_array("SELECT address FROM addresses WHERE person_id = ?", {}, $self->id);
 return $date;
}

但我想要的是:

has name => (is => 'ro', isa => 'Str', lazy => 1, builder => '_build_stuff');
has birthdate => (is => 'ro', isa => 'Date', lazy => 1, builder => '_build_stuff');
has address => (is => 'ro', isa => 'Address', lazy => 1, builder => '_build_address');
sub _build_stuff {
 my $self = shift;
 my ($name, $date) = $dbh->selectrow_array("SELECT name, birthdate FROM people WHERE id = ?", {}, $self->id);
 $self->name($name);
 $self->birthdate($date);
}
sub _build_address { 
 #same as before 
}

【问题讨论】:

    标签: perl moose


    【解决方案1】:

    在这种情况下,当我不想像 Ether 的回答那样拥有一个单独的对象时,我所做的就是为中间状态提供一个惰性构建的属性。所以,例如:

    has raw_row => (is => 'ro', init_arg => undef, lazy => 1, builder => '_build_raw_row');
    has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate');
    
    sub _build_raw_row {
       $dbh->selectrow_hashref(...);
    }
    
    sub _build_birthdate {
        my $self = shift;
        return $self->raw_row->{birthdate};
    }
    

    对名称等重复与birthdate相同的模式。

    读取任何单个属性将尝试从raw_row 获取数据,其惰性构建器只会运行一次 SQL。由于您的属性都是只读的,因此您不必担心在其中一个发生更改时更新任何对象状态。

    这种模式对于像 XML 文档这样的东西也很有用——你保存的中间状态可以是例如一个 DOM,各个属性是从 XPath 表达式或你有什么东西懒惰地构建的。

    【讨论】:

    • 不错的模式,但是如果有很多属性,有没有办法避免重复: build => '_build_attribute1' ... sub _build_attribute1 { my $self = shift;返回 $self->raw_row->{attribute1}; } 一遍又一遍地?也许 _build_attribute 有办法识别调用它的属性?
    • @yahermann,我认为这就是问题的关键。如果你能按照你的建议去做,这个问题就不存在了。
    【解决方案2】:

    不,属性构建器一次只能返回一个值。您可以通过让每个构建器在返回之前设置另一个属性的值来构建两者,但这很快就会变得丑陋......

    然而,如果您通常有两条数据以某种方式组合在一起(例如来自与您的情况相同的数据库查询),您可以将这些值一起存储在一个属性中一个对象:

    has birth_info => (
        is => 'ro', isa => 'MyApp::Data::BirthInfo',
        lazy => 1,
        default => sub {
             MyApp::Data::BirthInfo->new(shift->some_id)
        },
        handles => [ qw(birthdate name) ],
    );
    
    package MyApp::Data::BirthInfo;
    use Moose;
    has some_id => (
        is => 'ro', isa => 'Int',
        trigger => sub {
            # perhaps this object self-populates from the DB when you assign its id?
            # or use some other mechanism to load the row in an ORMish way (perhaps BUILD)
        }
    );
    has birthdate => (
        is => 'ro', isa => 'Str',
    );
    has name => (
        is => 'ro', isa => 'Str',
    );
    

    【讨论】:

    • 嗯。这可能会奏效,尽管它似乎增加了我试图降低它的复杂性。
    • @RickF:用一个构建器构建两个属性比在一个对象上存储两个属性更复杂。
    • 你可能是对的。我的犹豫主要是由于直觉水平的阻力,无法简单地以 Moose 形式重新创建复杂的表格结构。
    • 在记事本上,我会要求您在将答案标记为已接受之前清理一下示例代码。 qw() 中的逗号,在 trigger 子中缺少 },子对象中的属性之一应该是“name”而不是“birthdate”
    • 再次编辑了我的回复,以删除我试图潜入那里的光头谎言。 “不能”与“不应该”不同:)
    猜你喜欢
    • 1970-01-01
    • 2011-05-12
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 2014-10-11
    • 1970-01-01
    相关资源
    最近更新 更多