【问题标题】:Writing good object-oriented code under AnyEvent在 AnyEvent 下编写好的面向对象代码
【发布时间】:2012-06-15 07:28:17
【问题描述】:

我们正在构建一个包含复杂逻辑的大型应用程序,它由模块组成。我曾经用更简单的方法构建更大规模的方法,例如,

# fig. 1   
package Foo;
sub highlevel {
    my ($self, $user, $event) = @_;
    my $session = $self->get_session($user);
    my $result = $self->do_stuff($session, $event);
    $self->save_session($session);
    return $result;
};

(这当然是简化的)。返回结果,抛出异常,大家开心。

现在,我们正在转向 AnyEvent。我的模块不是最上层的,所以我不能这样做

# fig. 2
my $cv = AnyEvent->condvar;
# do stuff
return $cv->recv;

到目前为止,我见过的大多数 AE 模块都是这样工作的:

# fig. 3
$module->do_stuff( $input, 
    on_success => sub { ... }, 
    on_error => sub { ... }
);

所以我已经重写了较低级别的方法并尝试继续使用 highlevel() 和...

# fig. 4
package Foo;
sub highlevel {
    my ($self, $user, $event, %callbacks) = @_;
    my $done = $callbacks{on_success};
    my $error = $callbacks{on_error};
    $self->get_session( $user,
        on_error => $error,
        on_success => sub {
             my $session = shift;
             $self->do_stuff( $session, $event,
                  on_error => $error,
                  on_success => sub { 
                       my $result = shift;
                       $self->save_session( $session,
                            or_error => $error,
                            on_success => sub { $done->($result); }
                       );
                  }
              );
          }
     );
 };

不完全漂亮。我称之为“无限阶梯”。

现在我能想出的下一件事是一个临时状态机,其中 highlevel() 被分解为 _highlevel_stage1()、_highlevel_stage2() 等。但这也不能让我满意(它无法维护,而且想到好名字而不是 stageXX 让我头疼)。

我们已经在研究一个成熟的状态机来驱动整个应用程序,但是必须为每个交互添加一个转换对我来说有点过于慷慨了。

所以问题是:编写实现业务逻辑(图 1)以在 AnyEvent 应用程序(图 3)中运行的模块的最佳实践是什么?

【问题讨论】:

    标签: perl asynchronous anyevent


    【解决方案1】:

    执行摘要:您要么想要控制反转(带有 Coro 的线程),要么想要状态机。

    您可以使用 Coro,它可以将无限梯形图转换为线性代码(通过控制反转),例如使用 Coro::rouse_cb/rouse_wait 或一些 Coro::AnyEvent 函数:

       do_sth cb => sub { ...
    

    变成(如果回调只被调用一次):

       do_sth cb => Coro::rouse_cb;
       my @res = Coro::rouse_wait;
    

    您唯一的其他选择是使用状态机,例如使用对象(有很多方法可以实现状态机,尤其是在 Perl 中):

    my $state = new MyObject;
    
    do_something callback => sub { $state->first_step_done };
    

    在 first_step done 中,您为 $self->next_state_done 等注册一个回调。

    您还可以查看一些 cpan 模块,例如 AnyEvent::Blackboard 或 AnyEvent::Tools - 我自己没有使用过它们,但也许它们可以帮助您。

    至于 condvars,我个人不太热衷于将它们用作 API 的唯一手段 - 我更喜欢回调(因为 condvars 是有效的回调,这两者都允许),但 condvars 确实允许您在消费者中引发异常(通过呱呱方法)。

    【讨论】:

    • 感谢您的澄清。这给我留下了比最初更多的问题,但至少现在我可以自己去阅读了。
    【解决方案2】:

    您可能希望使用 Future 模块将其封装在 Future 对象中。这增加了语法糖以使这个更清洁。

    【讨论】:

      【解决方案3】:

      嗯,我能想到的一件事是使用稍微修改的责任链模式:

      my $params = {
        user => $user,
        event => $event,
        session => undef
      };
      
      my @chain = ('get_session', 'do_stuff', 'save_session', 'emit');
      
      for my $index (0..$#chain) {
        my $current = $chain[$index];
        my $next    = $chain[$index + 1] || undef;
        $self->{$current}($params, 
          on_error => sub { $self->error($params) },
          on_success => sub { $self->{$next}($params) },
        );
      }
      

      这有点粗糙,但我希望它能说明这一点。 )

      【讨论】:

        【解决方案4】:

        7年后再次访问这个问题,我似乎知道了正确答案:monad。协程、Future、Promise、async/await 和(一个理智的实现)有限状态机都是它的子类型。

        当然,the curse of the monad 阻止我向其他人解释它。

        如果我今天必须再做一次,我会带着某种承诺对象离开。

        【讨论】:

        • 某种承诺对象”正是Future提供的。
        • @melpomene 是的,我根据该基础更改了所选答案。虽然我没有从事我所要求的项目,但 7 年是很多时间。
        • 啊,我没有意识到你已经改变了他们接受的答案。
        猜你喜欢
        • 1970-01-01
        • 2010-09-25
        • 1970-01-01
        • 1970-01-01
        • 2012-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-05
        相关资源
        最近更新 更多