2012-06-15 10 views
6

Estamos construyendo una aplicación grande con lógica compleja, que se compone de módulos. He utilizado para construir métodos de mayor escala de los métodos más simples, por ejemplo,Escribiendo un buen código orientado a objetos bajo 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; 
}; 

(esto se simplifica por supuesto). Los resultados se devuelven, se lanzan excepciones, todos están contentos.

Ahora, estamos haciendo un cambio a AnyEvent. Mi módulo no es el nivel más alto, por lo que no puede hacer sólo

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

más AE que he visto hasta ahora funciona de esta manera:

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

Así que he terminado de reescribir el menor -level métodos y trataron de proceder con alto nivel() y ...

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

exactamente hermosa. Yo lo llamo "la escalera infinita".

Ahora lo siguiente que se me ocurre es una máquina de estado ad-hoc donde highlevel() se divide en _highlevel_stage1(), _highlevel_stage2() etc. Pero eso no me satisface también (es imposible de mantener, y pensar en buenos nombres en lugar de stageXX me da dolor de cabeza).

Ya estamos buscando una máquina de estado en toda regla para manejar toda la aplicación, pero tener que agregar una transición para cada interacción me parece demasiado generosa.

Entonces la pregunta es: ¿Cuáles son las mejores prácticas para escribir módulos que implementan la lógica de negocios (figura 1) para ejecutar dentro de una aplicación AnyEvent (figura 3)?

Respuesta

6

Resumen ejecutivo: o desea inversión de control (hilos con Coro que bloquea) o una máquina de estados.

Puede usar Coro, que puede convertir la escalera infinita en código lineal (por inversión de control), p. utilizando Coro :: rouse_cb/rouse_wait, o algunas de las funciones Coro :: AnyEvent:

do_sth cb => sub { ... 

se convierte (si la devolución de llamada sólo se le llama una vez):

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

Su única otra opción es utilizar una máquina de estado, por ejemplo el uso de un objeto (hay muchas maneras de implementar máquinas de estado, sobre todo en Perl):

my $state = new MyObject; 

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

Y en first_step hecho, se registra una devolución de llamada por $ self-> next_state_done etc.

Puede también buscar en algunos módulos de cpan, como AnyEvent :: Blackboard o AnyEvent :: Tools - No los he usado, pero quizás puedan ayudarte.

En cuanto a los condvars, personalmente no soy tan optimista sobre usarlos como el único medio para una API - prefiero las devoluciones de llamada (ya que los condvars son devoluciones de llamada válidas, esto permite ambas), pero los condvars te permiten plantear excepciones en el consumidor (a través del método croak).

+0

Gracias por aclarar. Esto me deja con muchas más preguntas de las que tenía inicialmente, pero al menos ahora puedo ir y leerme a mí mismo. – Dallaylaen

3

Bueno, una cosa que se me ocurre es el uso de la cadena ligeramente modificada del modelo de Responsabilidad:

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

Es un poco duro, pero espero que muestra el punto.)

1

Es posible que desee encapsularlo en un objeto futuro utilizando el módulo Future. Eso agrega azúcar sintáctico para hacer esto más limpio.

Cuestiones relacionadas