【问题标题】:Moose around method modifier, setter and constructor (new): intercept all updates to an attributeMoose around 方法修饰符、setter 和构造函数(新):拦截对属性的所有更新
【发布时间】:2012-02-22 12:39:45
【问题描述】:

更新

我在原始问题中发布的代码说明了方法修饰符起作用或不起作用的方式。 它不一定能说明我给出的问题描述。 这段代码应该是。它可以工作,但在我用来编写跟踪所有更新并根据提供给 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 或setter,我不在乎),我想确保它以整数形式结束(因此有约束)。如果它不是整数,我想用1 替换它并对对象进行一些其他更新,例如保存原始数量并将价格乘以原始数量。对于构造函数和设置器。

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

【问题讨论】:

  • 使用类型强制怎么样?
  • 类型强制只允许我作用于属性本身,而不是我可能需要更新的其他属性。所以类型强制只完成了部分工作。
  • 简而言之,你的设计很糟糕。你有一个领域有两个目的。我认为更好的设计是在获取时规范化quantityprice。这是否好取决于你如何使用你的对象。

标签: perl triggers moose method-modifier


【解决方案1】:

这个怎么样?

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;

【讨论】:

  • 没错。我现在应该知道了。谢谢!
  • 抱歉 - 在喝咖啡之前回复,总是一个错误。在这种情况下,您将如何连接逻辑以对其他对象成员(属性)进行额外的更新?当浮点到整数转换发生时,我必须存储原始数量并更新价格。将发布另一个更接近我的描述的代码示例。
  • 强制通常是在设置值时处理操作的最佳方式,但它不能处理所有 OP 的要求。
  • 接受,因为答案很有用,虽然不完全适合这个问题。
【解决方案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 设为派生值。

【讨论】:

  • 谢谢。我也不喜欢对象是有状态的这一事实。另一方面,还有什么办法可以解决?使用您的建议,将数量更新回整数值会产生错误的结果,因为无法检测到案例。我同意这总体上是不幸的,但它是为了连接需要一起工作的文章的两种表示。啊,对于明确的语言,+1。 :)
猜你喜欢
  • 2010-12-14
  • 2013-02-02
  • 1970-01-01
  • 1970-01-01
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 2013-09-02
  • 1970-01-01
相关资源
最近更新 更多