4

在对各种事件作出反应的 Perl 守护程序中,我试图通过创建匿名子例程在 2 种情况下使用Null 对象模式,它应该只返回一个值 1 aka “true”(请滚动到右侧查看检查子例程LOGINALIVE事件):

package User;

our %EVENTS = (
        LOGIN   => {handler => \&handleLogin,   check => sub {1},     },
        CHAT    => {handler => \&handleChat,    check => \&mayChat,   },
        JOIN    => {handler => \&handleJoin,    check => \&mayJoin,   },
        LEAVE   => {handler => \&handleLeave,   check => \&mayLeave,  },
        ALIVE   => {handler => sub {},          check => sub {1},     },
        BID     => {handler => \&handleBid,     check => \&checkArgs, },
        TAKE    => {handler => \&handleTake,    check => \&checkArgs, },
  # .... more events ....
);


sub action($$$) {
        my $user  = shift;
        my $event = shift;
        my $arg   = shift;
        my $game  = $user->{GAME};

        unless (exists $EVENTS{$event}) {
                print STDERR "wrong event: $event\n";
                return;
        }

        my $handler = $EVENTS{$event}->{handler};
        my $check   = $EVENTS{$event}->{check};

        return unless $user->$check->($arg); # XXX fails
        $user->$handler->($arg);
}

sub mayChat($$) {
        my $user = shift;

        return if $user->{KIBITZER};
}

# ...... more methods here ...

1;

不幸的是,我收到了LOGIN事件的运行时错误:

Can't use string ("1") as a subroutine ref while "strict refs" in use

有人知道如何在这里解决吗?

如何为匿名 Perl 子例程提供“函数指针”?

处理程序 => \&sub { 1 }也不这样做。

在 CentOS 5.x 和 6.x 上使用 perl 5.8.8 和 perl 5.10.1

更新:

我也试过以下:

    my $check = $EVENTS{$event}->{check};
    return unless $check->($user, $arg);

但这无济于事。我认为这排除了某些答案中建议的“缺失的祝福”。

更新 2:

我在原始问题中扩展了源代码片段。背景是:我正在重构我的源代码,因此我已经创建了上面列出的%EVENTS哈希,因此对于每个传入事件(从Flash 客户端通过 TCP-socket 发送的字符串)那里是对验证事件的子例程 ( check ) 的引用和对执行某些操作的另一个子例程 ( handler ) 的引用。我不确定其他子程序是否有效 - 我已经被第一个LOGIN 事件困住了。

我也不明白为什么不检查 => sub { 1 }上面的工作 - sub不应该返回对匿名子例程的引用(当名称被省略时 - 根据perldoc perlref第 4 节)?

更新 3:

打印 Dumper(\%EVENTS)的输出-

$VAR1 = {
          'PLAY' => {
                      'check' => sub { "DUMMY" },
                      'handler' => sub { "DUMMY" },
                    },
          'JOIN' => {
                      'check' => sub { "DUMMY" },
                      'handler' => sub { "DUMMY" },
                    },
          'OVER1' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'ALIVE' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'DISCARD' => {
                         'check' => $VAR1->{'PLAY'}{'check'},
                         'handler' => sub { "DUMMY" },
                       },
          'MISS1' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'LOGIN' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'TAKE' => {
                      'check' => $VAR1->{'PLAY'}{'check'},
                      'handler' => sub { "DUMMY" },
                    },
          'ONEMORE' => {
                         'check' => sub { "DUMMY" },
                         'handler' => sub { "DUMMY" },
                       },
          'OVER2' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'MISS2' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'EXACT' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'TRUST' => {
                       'check' => $VAR1->{'PLAY'}{'check'},
                       'handler' => sub { "DUMMY" },
                     },
          'LEAVE' => {
                       'check' => sub { "DUMMY" },
                       'handler' => sub { "DUMMY" },
                     },
          'DEFEND' => {
                        'check' => $VAR1->{'PLAY'}{'check'},
                        'handler' => sub { "DUMMY" },
                      },
          'OPEN' => {
                      'check' => $VAR1->{'PLAY'}{'check'},
                      'handler' => sub { "DUMMY" },
                    },
          'REVEAL' => {
                        'check' => sub { "DUMMY" },
                        'handler' => sub { "DUMMY" },
                      },
          'CHAT' => {
                      'check' => sub { "DUMMY" },
                      'handler' => sub { "DUMMY" },
                    },
          'DECLARE' => {
                         'check' => $VAR1->{'PLAY'}{'check'},
                         'handler' => sub { "DUMMY" },
                       },
          'BACK' => {
                      'check' => sub { "DUMMY" },
                      'handler' => sub { "DUMMY" },
                    },
          'MISERE' => {
                        'check' => sub { "DUMMY" },
                        'handler' => sub { "DUMMY" },
                      },
          'BID' => {
                     'check' => $VAR1->{'PLAY'}{'check'},
                     'handler' => sub { "DUMMY" },
                   }
        };
4

6 回答 6

3

$check已经是代码参考,所以你可以说

return unless $check->($arg);

$check如果对返回代码引用的代码的引用也可以挽救您现有的代码:

our %EVENTS = ( LOGIN => { ..., check => sub { sub { 1 } }, } ... );

将创建标量引用的运算符或创建数组引用的运算符的sub { }方式视为“代码引用”运算符。\[...]

于 2012-03-29T17:08:06.177 回答
3

问题不在于出现问题的特定事件;实际的错误在action. 特别是,线

    return unless $user->$check->($arg); # XXX fails

不做你认为它做的事。在原型的存在和 Perl 愿意尝试调用由名称指定的 sub 之间,您最终会调用User::CHAT事件。这似乎不是您打算这样做的。

更正确的调用看起来像

    return unless $check->($user, $arg);

这期望$check包含一个子引用(它确实如此),取消引用它并调用它。即使有时$check会引用原型函数,这仍然有效。

这留下了这个过程代码不尊重继承的问题。为此,您必须改写%EVENTS一下。因此:

our %EVENTS = (
        LOGIN   => {handler => \&handleLogin,   check => sub {1},     },
        CHAT    => {handler => \&handleChat,    check => sub { shift->mayChat(@_) },
        ...
);

请注意,强烈建议您不要将函数原型和 Perl OO 编程混合使用,因为这会导致像这样难以诊断的问题。

关于您的另一个问题: my $foo = sub { }您确实是如何构造匿名子例程的。但是您确实需要适当地调用它们。

于 2012-03-29T21:37:55.080 回答
2

除非$check是代码参考,

$user->$check->($arg);

不会工作。

作为一个独立的例子:

    > perl -e 'use strict;use warnings;my $a=17;$a->("whatever");'
Can't use string ("17") as a subroutine ref while "strict refs" in use at -e line 1.

因此,您必须更仔细地查看数据的结构,并避免将标量视为代码引用。

于 2012-03-29T16:51:06.753 回答
2

看起来 $event 要么是 LOGIN 要么是 ALIVe,两者都有用于返回 1 的检查键的匿名子程序。$check 在本地定义到该子例程,并返回 1,然后代码尝试访问用户哈希中的值“1” /object 作为哈希

于 2012-03-29T17:05:03.307 回答
1

我自己的答案 - 以下似乎有效,我可能以错误的方式取消引用我的子参考......

sub action($$$) {
        my $user  = shift;
        my $event = shift;
        my $arg   = shift;

        my $handler = $EVENTS{$event}->{handler};
        my $check   = $EVENTS{$event}->{check};

        return unless &$check($user, $arg);
        &$handler($user, $arg);
}
于 2012-03-29T20:08:31.470 回答
0

关于您的程序,您没有提出问题的关键事实是: mayChat并且朋友都返回子例程引用。然后对这些进行评估

    return unless $user->$check->($arg); # XXX falis

. 中的条目%EVENTS是对返回子例程引用的例程的引用。一旦我们用这些术语说明了它,您的原始代码的问题就变得很明显了。

$EVENTS{LOGIN}->{check}是对返回整数的 sub 的引用,而预期是对返回 sub 的引用的 sub 的引用。如果我们将它作为对返回子引用的子引用的引用:

    LOGIN   => {phase => undef,      handler => \&handleLogin,   check => sub { sub {1} },     },

, 有用。

解决您的问题的方法是 (1) 使这些条目成为返回 subs 的 subs 并 (2) 记录界面,%EVENTS因此您是最后一个遇到此问题的人。

于 2012-03-29T19:01:18.007 回答