观察 Perl 中属性的变化 class

Watch change of attribute inside Perl class

任何人都可以提供一个代码示例,您如何在 class 内部的变量更改上设置观察者?我尝试使用不同的功能 (Scalar::Watcher, trigger attribute of Moo) 和 OOP 框架 (Moo, Mojo::Base) 以多种方式完成它,但都失败了。

为了更好地理解我的任务,下面是我失败的代码。在此示例中,每次 attr1 更改时我都需要更新 attr2。

使用 Mojo::Base 和 Scalar::Watcher:

package Cat;
use Mojo::Base -base;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => 1;
has 'attr2' => 2;

has 'test' => sub { # "fake" attribute for getting access to $self
  my $self = shift;
  when_modified $self->attr1, sub { $self->attr2(3); say "meow" };
};


package main;
use Data::Dumper;

my $me = Cat->new;
$me->attr1;
warn Dumper $me;
say $me->attr1(3)->attr2; # attr2 is still 2, but must be 3

使用 Moo 和触发器:

package Cat;
use Moo;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => ( is => 'rw', default => 1, trigger => &update() ); 
has 'attr2' => ( is => 'rw', default => 1);

sub update {
  my $self = shift;
  when_modified $self->attr1, sub { $self->attr2(3); say "meow" }; # got error here: Can't call method "attr1" on an undefined value
};


package main;
use Data::Dumper;

my $me = Cat->new;
$me->attr1;
warn Dumper $me;
say $me->attr1(3)->attr2;

非常感谢任何建议。

Moo 部分

got error here: Can't call method "attr1" on an undefined value

这是因为 Moo 期望 has 的代码引用为 trigger。您正在将调用结果传递给 update。这里的 & 并没有给你一个引用,而是告诉 Perl 忽略 update 函数的原型。你不想要那个。

相反,使用 \&foo 创建引用并且不添加括号 ()。你不想调用这个函数,你想引用它。

has 'attr1' => ( is => 'rw', default => 1, trigger => \&update );

一旦您完成了该操作,您就不再需要 Scalar::Watcher 了。触发器已经做到了。每次 attr1 更改时都会调用它。

sub update {
    my $self = shift;
    $self->attr2(3);
    say "meow";
};

如果你现在 运行 整个事情,它会工作一点,但崩溃并出现此错误:

Can't locate object method "attr2" via package "3" (perhaps you forgot to load "3"?) at

那是因为 attr1 return 是新值,而不是对 $self 的引用。所有 Moo/Moose 访问器都是这样工作的。 3 不是对象,所以它没有方法 attr2

#       this returns 1
#               |
#               V
say $me->attr1(3)->attr2;

而是分两次调用。

$me->attr1(3);
say $me->attr2;

这是一个完整的例子。

package Cat;
use Moo;

use feature 'say';

has 'attr1' => ( is => 'rw', default => 1, trigger => \&update );
has 'attr2' => ( is => 'rw', default => 1 );

sub update {
    my $self = shift;
    $self->attr2(3);
    say "meow";
}

package main;
my $me = Cat->new;

say $me->attr2;
$me->attr1(3);
say $me->attr2;

并且输出:

1
meow
3

为什么 Scalar::Watcher 不适用于 Mojo

首先,Mojo::Base没有提供触发机制。但是您实现 Scalar::Watcher 的方式无法工作,因为从未调用过 test 方法。我尝试在基于 Mojo::Base 的 class 中挂钩 new 以在始终被调用的地方进行 when_modified 调用。

这里的一切都只是猜测。

以下代码段是我尝试过的,但它不起作用。我将在下面进一步解释原因。

package Cat;
use Mojo::Base -base;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => '1';
has 'attr2' => 'original';

sub new {
    my $class = shift;

    my $self = $class->SUPER::new(@_);
    when_modified $self->{attr1}, sub { $self->attr2('updated'); say "meow" };

    return $self;
}

如您所见,这现在是 new 调用的一部分。代码确实得到执行。但是没用。

documentation of Scalar::Watcher states 观察者应该在那里直到变量超出范围。

If when_modified is invoked at void context, the watcher will be active until the end of $variable's life; otherwise, it'll return a reference to a canceller, to cancel this watcher when the canceller is garbage collected.

但实际上我们没有标量变量。如果我们尝试做

when_modified $self->foo

然后 Perl 在 $self 上执行 foo 的方法调用,when_modified 将获得该调用的 return 值。我也尝试进入上面对象的内部结构,但这也没有用。

我的 XS 不够强大,无法理解正在发生的事情 here,但我认为它在附加魔法时遇到了一些问题。它不能与哈希引用值一起使用。可能这就是为什么它被称为 Scalar::Watch.