Moose around method modifier,setter和constructor(new):拦截属性的所有更新

时间:2012-01-30 18:14:09

标签: perl triggers moose method-modifier

更新

我在原始问题中发布的代码说明了方法修饰符执行或不执行的方式。 它不一定说明我给出的问题描述。 这段代码应该是。它有效,但在触发器中包含一个黑客,我用它来编码跟踪所有更新的要求,并根据提供给setter的值对它们进行操作。

package Article;
use Moose;
use Moose::Util::TypeConstraints;
has 'name',                 is => 'rw', isa => 'Str', required => 1;
has 'price',                is => 'rw', isa => 'Num', required => 1;
has 'quantity',             is => 'rw', isa => 'Num', required => 1,
                            trigger => \&update_quantity;
has 'quantity_original',    is => 'rw', isa => 'Num',
                            predicate   => 'quantity_fix',
                            clearer     => 'quantity_back_to_normal';

# https://metacpan.org/module/Moose::Cookbook::Basics::Recipe3
# A trigger accepts a subroutine reference, which will be called as a method
# whenever the attribute is set. This can happen both during object
# construction or later by passing a new object to the attribute's accessor
# method. However, it is not called when a value is provided by a default or
# builder.

sub update_quantity {
    my( $self, $val ) = @_;
#   print STDERR $val, "\n";
    if ( $val == int $val ) {
        $self->quantity_back_to_normal;
    } else {
        $self->quantity_original( $val );
        # Updating quantity via setter would retrigger this code.
        # Which would defeat its purpose. The following won't:
        $self->{quantity} = 1; # hack, yes; but it does work
    }
}

around name => sub {
    my $orig = shift;
    my $self = shift;
    return $self->$orig( @_ ) if @_; # setter
    return $self->$orig unless $self->quantity_fix;
    return sprintf '%s (%s)', $self->$orig, $self->quantity_original;
};

around price => sub {
    my $orig = shift;
    my $self = shift;
    return $self->$orig( @_ ) if @_; # setter
    return $self->$orig unless $self->quantity_fix;
    return int( 100 * $self->$orig * $self->quantity_original + 0.5 ) / 100;
};

__PACKAGE__->meta->make_immutable; no Moose;

package main;
use Test::More;

{   my $art = Article->new( name => 'Apfel', price => 33, quantity => 4 );
    is $art->price, 33, 'supplied price';
    is $art->quantity, 4, 'supplied quantity';
    is $art->name, 'Apfel', 'supplied name';
}

{   my $art = Article->new( name => 'Mehl', price => 33, quantity => 4.44 );
#   diag explain $art;
    is $art->quantity, 1, 'has quantity fixed';
    is $art->price, 33 * 4.44, 'has price fixed';
    is $art->name, 'Mehl (4.44)', 'has name fixed';
    # tougher testing ...
    $art->quantity(3);
    is $art->quantity, 3, 'supplied quantity again';
    is $art->price, 33, 'supplied price again';
    is $art->name, 'Mehl', 'supplied name again';
}

done_testing;

仍然不确定使用Moose设施来完成这项工作。 丰富的功能和设施并不总是让事情变得更容易。 至少不是当你试图不重新发明轮子并重复使用可以重复使用的轮子时。

原始问题

看来around方法修饰符不是作为构建对象的一部分调用的(调用new时)。测试用例:

package Bla;
use Moose;
has 'eins', is => 'rw', isa => 'Int';
has 'zwei', is => 'rw', isa => 'Num';

around [qw/ eins zwei /] => sub {
    my $orig = shift;
    my $self = shift;
    return $self->$orig unless @_;
    my $val = shift;
    if ( $val == int $val ) {
        return $self->$orig( $val );
    }
    else {
        return $self->$orig( 1 );
        warn "replaced $val by 1";
    }
};

package main;
use Test::More;
use Test::Exception;

dies_ok { Bla->new( eins => 33.33 ) } 'dies because of Int type constraint';
my $bla = Bla->new( zwei => 22.22 );
is $bla->zwei, 22.22, 'around has not been called';
done_testing;

让我解释一下我想要实现的目标。有一个类quantityprice(还有一些州)。当数量进来时(通过new或设定者,我不在乎),我想确保它最终成为一个整数(因此约束)。如果它不是整数,我想仅用1替换它并对对象进行一些其他更新,例如保存原始数量并将价格乘以原始数量。对于构造函数和setter都是。

我该怎么办?提供完成工作的子程序,并从around BUILDARGSaround quantity调用它?

2 个答案:

答案 0 :(得分:2)

这个怎么样?

package Bla;
use Moose;
use Moose::Util::TypeConstraints;

subtype 'MyInt',
  as 'Int';

coerce 'MyInt',
  from 'Num',
  via { 1 };

has 'eins', is => 'rw', isa => 'Int';
has 'zwei', is => 'rw', isa => 'MyInt', coerce => 1;

package main;
use Test::More;
use Test::Exception;

dies_ok { Bla->new( eins => 33.33 ) } 'dies because of Int type constraint';
my $bla = Bla->new( zwei => 22.22 );
is $bla->zwei, 1, '22.22 -> 1';

my $bla2 = Bla->new( zwei => 41 );
is $bla2->zwei, 41, '41 -> 41';

done_testing;

答案 1 :(得分:2)

当我继续靠墙跑时,我知道我做错了什么,而且我正在墙上奔跑。设计很糟糕。我认为关键问题是你有一个领域有两个目的。

如果orig_quantity的唯一目的是规范价格,我建议您在设置后将quantityprice标准化。这可以明确地完成,或者当您尝试获取它们时可以隐式完成,如下所示。

has price => (
   accessor => '_price',
   isa      => 'Num',
   handles  => {
      price => sub {
         my $self = shift;
         return $self->_price(@_) if @_;
         $self->normalize();
         return $self->_price();
      },
   },
);

has quantity => (
   accessor => '_quantity',
   isa      => 'Num',
   handles  => {
      quantity => sub {
         my $self = shift;
         return $self->_quantity(@_) if @_;
         $self->normalize();
         return $self->_quantity();
      },
   },
);

sub normalize {
   my ($self) = @_;
   my $quantity = $self->_quantity();
   return if is_an_int($quantity);
   $self->_quantity(1);
   $self->_price($self->_price() / $quantity);
}

如果您确实需要orig_quantity,那么您可能希望构造函数直接设置它并使quantity成为派生值。