1

更新

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

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或设置器,我不在乎),我想确保它以整数形式结束(因此是约束)。如果它不是整数,我想用 just 替换它1并对对象进行一些其他更新,比如保存原始数量并将价格乘以原始数量。对于构造函数和设置器。

我该怎么办?提供一个完成这项工作的子程序并从around BUILDARGS和调用它around quantity

4

2 回答 2

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;
于 2012-01-30T21:46:55.830 回答
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生成派生值。

于 2012-01-31T11:00:10.750 回答