2012-06-15 45 views
6

我們正在構建一個由模塊組成的複雜邏輯的大型應用程序。我曾經建立更大規模的簡單方法的方法,例如,在AnyEvent下編寫好的面向對象的代碼

# 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 { ... } 
); 

所以我做了改寫較低水平的方法,並試圖進行高級()和...

# 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); } 
         ); 
        } 
      ); 
      } 
    ); 
}; 

不完全美麗。我稱之爲「無限階梯」。

現在接下來我想到的是一個ad-hoc狀態機,其中highlevel()分解爲_highlevel_stage1(),_highlevel_stage2()等等。但是這並不能讓我滿意(它是不可維護的,並想到好名字而不是stageXX讓我頭疼)。

我們已經在研究一個全面的狀態機來驅動整個應用程序,但不得不爲添加一個轉換,每個交互對我來說看起來有點太慷慨。

所以問題是:編寫實現業務邏輯(圖1)在AnyEvent應用程序中運行的模塊(圖3)的最佳實踐是什麼?

回答

6

執行摘要:您可能想要控制反轉(帶有阻止Coro的線程)或狀態機。

您可以使用Coro,它可以將無限梯級轉換爲線性代碼(通過控制反轉),例如,使用科羅:: rouse_cb/rouse_wait,或一些科羅:: 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,你註冊一個回調$自我> next_state_done等

你也可以看插入一些cpan模塊,比如AnyEvent :: Blackboard或AnyEvent :: Tools - 我自己並沒有使用它們,但也許它們可以幫助你。

對於condvars,我個人並不熱衷於將它們用作API的唯一手段 - 我更喜歡回調(因爲condvars是有效的回調,這允許兩者),但是condvars確實允許您在消費者中引發異常(通過croak方法)。

+0

感謝您的澄清。這給我留下了比我最初更多的問題,但至少現在我可以去閱讀自己了。 – Dallaylaen

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) }, 
); 
} 

這是一個有點粗糙,但我希望它顯示的點。 )

1

您可能想要使用Future模塊將其封裝在Future對象中。這增加了語法糖,使這個更清潔。