【问题标题】:Watch change of attribute inside Perl class观察 Perl 类中属性的变化
【发布时间】:2017-04-10 15:00:17
【问题描述】:

任何人都可以提供一个代码示例,您如何在类内的变量更改上设置观察者?我尝试使用不同的功能(Scalar::Watchertrigger 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;

非常感谢任何建议。

【问题讨论】:

  • Mojo::Base 的问题在于它不应该是一个通用的对象系统。它提供了简单的行为并且非常快。我们始终支持使用 Moo(se) 构建您的 Mojolicious 应用程序?当你想要这样的行为时,那是最好的解决方案!

标签: perl oop watch mojolicious moo


【解决方案1】:

Moo 部分

此处出现错误:无法在未定义的值上调用方法“attr1”

这是因为 Moo 期望 trigger 的代码引用为 has。您将调用结果传递给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";
};

如果你现在运行整个程序,它会运行一点点,但是会因为这个错误而崩溃:

无法通过包“3”定位对象方法“attr2”(也许您忘记加载“3”?)

这是因为attr1 返回新值,而不是对$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 的类中挂接 new,以便在总是调用它的地方调用 when_modified

这里的一切都只是猜测。

下面的sn-p是我试过的,但是不行。我将在下面进一步解释原因。

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,观察者应该在那里,直到变量超出范围。

如果 when_modified 在 void 上下文中被调用,观察者将被 活跃到 $variable 的生命结束;否则,它将返回一个 对取消者的引用,当取消者被取消时取消这个观察者 垃圾收集。

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

when_modified $self->foo

然后Perl 对$self 执行foo 的方法调用,when_modified 将获得该调用的返回值。我也尝试过深入到上面对象的内部,但也没有用。

我的 XS 不够强大,无法理解 here 发生的事情,但我认为附加这种魔法有些麻烦。它不能与哈希参考值一起使用。可能这就是它被称为 Scalar::Watch 的原因。

【讨论】:

  • 很好,可以,非常感谢您的详细解释!如果有人评论 Mojo::Base + Scalar::Watcher 部分会很高兴 :) 现在我正在做出一些架构决策,并希望避免使用 Moo 或 Moose 以尽可能不超重应用程序。
  • 我正在努力。我认为你不能让它发挥作用。而且,它很贵。 Scalar::Watcher 的文档甚至说你应该只将它用于调试。改用 Moo。它的重量很轻。除非你在树莓上运行,否则应该没问题。
  • "如果你运行它,它会工作一点,但会因为这个错误而崩溃:" 这部分我花了一段时间才理解,因为看起来你是仍在谈论update 子例程,而您实际上指的是OP 代码的main 包中的调用代码。
  • @Borodin 我很着急。我稍后会更清楚。如果需要,请随意编辑。
猜你喜欢
  • 1970-01-01
  • 2011-05-27
  • 2017-09-13
  • 2013-10-24
  • 1970-01-01
  • 1970-01-01
  • 2017-09-07
  • 1970-01-01
  • 2010-12-06
相关资源
最近更新 更多