2016-04-04 31 views
11

Ho diversi metodi CompletionStage che mi piacerebbe concatenare. Il problema è che il risultato del primo determinerà se i prossimi devono essere eseguiti. In questo momento l'unico modo per ottenere questo sembra passare gli argomenti "speciali" al prossimo CompletionStage in modo che non esegua il codice completo. Per esempio:Concatenamento di più CompletionStage solo se si raggiunge una condizione

public enum SomeResult { 
    RESULT_1, 
    RESULT_2, 
    RESULT_3 
} 

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) { 

    return CompletableFuture.supplyAsync(() -> { 
     // loooooong operation 
     if (someCondition) 
      return validValue; 
     else 
      return null; 
    }).thenCompose(result -> { 
     if (result != null) 
      return someMethodThatReturnsACompletionStage(result); 
     else 
      return CompletableFuture.completedFuture(null); 
    }).thenApply(result -> { 
     if (result == null) 
      return ChainingResult.RESULT_1; 
     else if (result.someCondition()) 
      return ChainingResult.RESULT_2; 
     else 
      return ChainingResult.RESULT_3; 
    }); 
} 

Dal momento che l'intero codice dipende il primo someCondition (se è false allora il risultato sarà RESULT_1, se poi non dovrebbe essere eseguito l'intero codice) questa costruzione sembra un po 'brutto per me. C'è un modo per decidere se i metodi 2nd (thenCompose(...)) e 3rd (thenApply(...)) devono essere eseguiti?

risposta

7

Si può fare in questo modo:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) { 
    CompletableFuture<SomeResult> shortCut = new CompletableFuture<>(); 
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>(); 

    CompletableFuture.runAsync(() -> { 
     // loooooong operation 
     if (someCondition) 
      withChain.complete(validValue); 
     else 
      shortCut.complete(SomeResult.RESULT_1); 
    }); 
    return withChain 
     .thenCompose(result -> someMethodThatReturnsACompletionStage(result)) 
     .thenApply(result -> 
        result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3) 
     .applyToEither(shortCut, Function.identity()); 
} 

Invece di un CompletableFuture creiamo due, che rappresentano i diversi percorsi di esecuzione può essere prelevata. L'operazione loooooong è presentata come eseguibile allora e completerà deliberatamente uno di questi CompletableFuture. Le fasi successive sono concatenate allo stadio che rappresenta la condizione soddisfatta, quindi entrambi i percorsi di esecuzione si uniscono all'ultimo passaggio applyToEither(shortCut, Function.identity()).

Il futuro shortCut ha già il tipo del risultato finale e sarà completata con la RESULT_1, il risultato della vostra null percorso che passa, che farà sì che il completamento immediato di tutta l'operazione. Se non ti piace la dipendenza tra il primo stadio ed il valore del risultato effettivo della scorciatoia è possibile allontanare in questo modo:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) { 
    CompletableFuture<Object> shortCut = new CompletableFuture<>(); 
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>(); 

    CompletableFuture.runAsync(() -> { 
     // loooooong operation 
     if (someCondition) 
      withChain.complete(validValue); 
     else 
      shortCut.complete(null); 
    }); 
    return withChain 
     .thenCompose(result -> someMethodThatReturnsACompletionStage(result)) 
     .thenApply(result -> 
        result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3) 
     .applyToEither(shortCut.thenApply(x -> SomeResult.RESULT_1), Function.identity()); 
} 

Se il terzo passo non è stata esemplare, ma sembra esattamente mostrato in questione, è possibile unire con il passo percorso di codice giunzione:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) { 
    CompletableFuture<ResultOfSecondOp> shortCut = new CompletableFuture<>(); 
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>(); 

    CompletableFuture.runAsync(() -> { 
     // loooooong operation 
     if (someCondition) 
      withChain.complete(validValue); 
     else 
      shortCut.complete(null); 
    }); 
    return withChain 
     .thenCompose(result -> someMethodThatReturnsACompletionStage(result)) 
     .applyToEither(shortCut, result -> result==null? SomeResult.RESULT_1: 
      result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3); 
} 

allora abbiamo solo saltare il secondo passaggio, il someMethodThatReturnsACompletionStage invocazione, ma che può ancora riposare per una lunga catena di passaggi intermedi, tutti i saltati senza la necessità di eseguire un salto manuale tramite nullcheck.

+0

Funziona, grazie! Seguendo lo stesso schema (creando diversi 'CompletableFuture' e usando' applyToEither (...) ') allora potrebbe essere possibile estenderlo a più percorsi, giusto? – Pelocho

+1

Sì, è possibile estenderlo a più percorsi, ma occorre fare attenzione a mantenere il codice risultante gestibile. Forse aiuta a incapsulare la logica di un ramo in un metodo di utilità che puoi usare più volte. – Holger

0

Per ragioni di completezza Sto aggiungendo una nuova risposta

Anche se la soluzione proposta da @Holger grandi opere è un pò strano per me. La soluzione Sto usando comporta la separazione diversi flussi in chiamate di metodo diversi e concatenamento con thenCompose:

public enum SomeResult { 
    RESULT_1, 
    RESULT_2, 
    RESULT_3 
} 

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) { 

    return CompletableFuture.supplyAsync(() -> { 
     // loooooong operation 
     if (someCondition) 
      return operateWithValidValue(value); 
     else 
      return CompletableFuture.completedValue(ChainingResult.RESULT_1); 
    }) 
     .thenCompose(future -> future); 

public CompletionStage<SomeResult> operateWithValidValue(... value) { 
    // more loooong operations... 
    if (someCondition) 
     return CompletableFuture.completedValue(SomeResult.RESULT_2); 
    else 
     return doFinalOperation(someOtherValue); 
} 

public CompletionStage<SomeResult> doFinalOperation(... value) { 
    // more loooong operations... 
    if (someCondition) 
     return CompletableFuture.completedValue(SomeResult.RESULT_2); 
    else 
     return CompletableFuture.completedValue(SomeResult.RESULT_3); 
} 

NOTA: Ho cambiato l'algoritmo dalla questione in bene di una risposta più completa

Tutte le operazioni lunghe potrebbero essere potenzialmente racchiuse in un altro CompletableFuture.supplyAsync con un piccolo sforzo