2012-05-22 9 views
38

Sto cercando un modello Java per creare una sequenza nidificata di chiamate di metodo non bloccanti. Nel mio caso, alcuni codici client devono richiamare in modo asincrono un servizio per eseguire alcuni casi d'uso e ogni fase di tale caso d'uso deve essere eseguita in modo asincrono (per ragioni che esulano dall'ambito di questa domanda). Immaginate Ho interfacce esistenti come segue:Pattern Java per callback annidati?

public interface Request {} 

public interface Response {} 

public interface Callback<R extends Response> { 
    void onSuccess(R response); 
    void onError(Exception e); 
} 

Ci sono diverse implementazioni accoppiate delle Request e Response interfacce, vale a dire RequestA + ResponseA (fornite dal cliente), RequestB + ResponseB (utilizzato internamente dal servizio), ecc .

flusso

Il trattamento è simile al seguente:

Sequence diagram showing nested callbacks.

Tra la ricezione di ciascuna risposta e l'invio della richiesta successiva, è necessario che avvenga un'ulteriore elaborazione (ad es. in base ai valori in una qualsiasi delle precedenti richieste o risposte).

Finora ho provato due approcci per la codifica questo in Java:

  • classi anonime: diventa brutto rapidamente a causa delle nidificazione
  • classi interne richieste: più ordinato rispetto al precedente, ma ancora difficile per un altro sviluppatore per comprendere il flusso di esecuzione

C'è qualche schema per rendere questo codice più leggibile? Ad esempio, potrei esprimere il metodo di servizio come un elenco di operazioni autonome che vengono eseguite in sequenza da alcune classi di framework che si occupano del nesting?

+0

So che hai detto che il non blocco era una necessità, ma esiste un modo per riconsiderare questo? Potresti creare un secondo thread che blocchi su ogni richiesta, ad esempio? Ho potuto vedere che il codice è molto chiaro con cura. –

+1

le operazioni autonome saranno ancora in qualche forma di classi, anonime o meno, ma ancora fisicamente presenti. Dovremo aspettare che il progetto lambda abbia costrutti più naturali per cose come te. Immagino anche che tu abbia bisogno di tutto questo nei paradigmi "plain java"? Non sembra che ti piacerebbe un framework di orchestrazione esterna –

+0

@Rob I: purtroppo, il non blocco è un must. –

risposta

10

Poiché l'implementazione (non solo l'interfaccia) non deve bloccare, mi piace l'idea dell'elenco.

Impostare un elenco di "operazioni" (forse Future s?), Per cui l'installazione dovrebbe essere abbastanza chiara e leggibile. Quindi, dopo aver ricevuto ciascuna risposta, dovrebbe essere richiamata l'operazione successiva.

Con un po 'di immaginazione, questo suona come lo chain of responsibility. Ecco alcuni pseudocodici per quello che sto immaginando:

public void setup() { 
    this.operations.add(new Operation(new RequestA(), new CallbackA())); 
    this.operations.add(new Operation(new RequestB(), new CallbackB())); 
    this.operations.add(new Operation(new RequestC(), new CallbackC())); 
    this.operations.add(new Operation(new RequestD(), new CallbackD())); 
    startNextOperation(); 
} 
private void startNextOperation() { 
    if (this.operations.isEmpty()) { reportAllOperationsComplete(); } 
    Operation op = this.operations.remove(0); 
    op.request.go(op.callback); 
} 
private class CallbackA implements Callback<Boolean> { 
    public void onSuccess(Boolean response) { 
     // store response? etc? 
     startNextOperation(); 
    } 
} 
... 
+1

Questo mi ha messo sulla strada giusta, quindi l'ho accettato. Ha finito per essere più simile a una lista collegata, con ogni operazione che ha un riferimento alla seguente operazione, come spiegato in questo post del blog: http://seewah.blogspot.sg/2009/02/gwt-tips-1-chain- of-responsibility.html (nel mio caso il codice era più complicato perché c'erano più tipi di richiesta e risposta). –

2

È possibile utilizzare il modello di calcolo degli attori. Nel tuo caso, il cliente, i servizi e le callback [B-D] possono essere tutti rappresentati come attori.

Ci sono molte librerie di attori per java. La maggior parte di loro, tuttavia, è di peso massimo, quindi ne ho scritto uno compatto ed estendibile: df4j. Considera il modello degli attori come un caso specifico di un modello di calcolo del flusso di dati più generale e, di conseguenza, consente all'utente di creare nuovi tipi di attori, per soddisfare in modo ottimale le esigenze degli utenti.

+0

Sembra che tu sia l'autore di df4j, nel qual caso penso che dovresti rivelarlo al momento di raccomandarlo. –

+1

Sì, sono l'autore di df4j. La risposta è stata aggiornata. –

+0

Esistono esempi di utilizzo di df4j per risolvere in particolare il problema del callback annidato? –

6

A mio parere, il modo più naturale per modellare questo tipo di problema è con Future<V>.

Quindi, anziché utilizzare una richiamata, è sufficiente restituire un "thunk": uno Future<Response> che rappresenta la risposta che sarà disponibile in futuro.

Quindi è possibile modellare i passaggi successivi come Future<ResponseB> step2(Future<ResponseA>) o utilizzare ListenableFuture<V> da Guava. Quindi puoi usare Futures.transform() o uno dei suoi overload per concatenare le tue funzioni in modo naturale, ma mantenendo comunque la natura asincrona.

Se utilizzato in questo modo, Future<V> si comporta come una monade (in effetti, penso che possa qualificarsi come uno solo, anche se non ne sono sicuro), e quindi l'intero processo sembra un po 'come IO in Haskell eseguito tramite la monade IO.

+0

Un problema di j.u.c.Future è che Future.get() non può essere chiamato da un'attività che si esegue in j.u.c.Executor (potrebbe causare un deadlock), solo da un thread separato. –

+0

@AlexeiKaigorodov: Penso che sia sicuro finché ogni attività ha il suo 'Executor' (che è sostanzialmente quello che hai detto).Questa è probabilmente la miglior ragione per usare 'ListenableFuture ': per proteggerti dal bloccare una chiamata a 'Future.get()'. –

1

Non sono sicuro di avere una domanda corretta. Se si desidera richiamare un servizio e al suo completamento, è necessario passare a un altro oggetto che può continuare l'elaborazione utilizzando il risultato.Puoi guardare usando Composite e Observer per ottenere questo.

Problemi correlati