2012-05-22 13 views
38

Poszukuję wzorca Java do tworzenia zagnieżdżonej sekwencji nie blokujących wywołań metod. W moim przypadku, kod klienta musi asynchronicznie wywoływać usługę, aby wykonać pewien przypadek użycia, a każdy krok tego przypadku użycia musi być wykonany asynchronicznie (z przyczyn spoza zakresu tego pytania). Wyobraźmy sobie, mam istniejących interfejsów następująco:Wzorzec Java dla zagnieżdżonych wywołań zwrotnych?

public interface Request {} 

public interface Response {} 

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

Istnieją różne Sparowane implementacje interfejsów Request i Response, mianowicie RequestA + ResponseA (podanych przez klienta), RequestB + ResponseB (używany wewnętrznie przez serwis), etc. .

przepływu

przetwarzanie wygląda następująco:

Sequence diagram showing nested callbacks.

Pomiędzy otrzymaniem każdej odpowiedzi a wysłaniem następnego żądania, musi nastąpić dodatkowe przetwarzanie (np. na podstawie wartości z któregokolwiek z poprzednich wniosków lub odpowiedzi).

Do tej pory próbowałem dwa podejścia do kodowania w Java:

  • klas anonimowych: szybko staje się brzydka z powodu wymaganych lęgowych
  • klas wewnętrznych: neater niż wymienione powyżej, ale nadal trudno inny program do zrozumienia przepływu wykonania:

Czy jest jakiś wzór, aby ten kod był bardziej czytelny? Na przykład, czy mogę wyrazić metodę usługi jako listę samodzielnych operacji, które są wykonywane kolejno przez niektóre klasy framework, które zajmują się zagnieżdżaniem?

+0

Wiem, że powiedziałeś, że nie blokowanie było koniecznością, ale czy jest jakikolwiek sposób, aby to ponownie rozważyć? Czy możesz na przykład utworzyć drugi wątek blokujący każde żądanie? Widziałem, że kod jest bardzo przejrzysty i ostrożny. –

+1

samodzielne operacje będą nadal w jakiejś formie klas, anonimowe lub nie, ale nadal fizycznie obecne. będzie musiał poczekać, aż projekt lambda będzie miał bardziej naturalne konstrukcje dla takich rzeczy jak ty. Zakładam też, że potrzebujesz tego wszystkiego w paradygmatach "plain java"? Nie brzmi tak, jakbyś chciał zewnętrznej ramy orkiestracji –

+0

@Rob I: Niestety, nie blokowanie jest koniecznością. –

Odpowiedz

10

Ponieważ implementacja (nie tylko interfejs) nie może blokować, podoba mi się pomysł na liście.

Skonfiguruj listę "operacji" (być może Future s?), Dla których konfiguracja powinna być całkiem przejrzysta i czytelna. Następnie po otrzymaniu każdej odpowiedzi należy wywołać następną operację.

Przy odrobinie wyobraźni brzmi to jak chain of responsibility. Oto jakiś pseudokod dla tego co wyobrażam sobie:

+1

To postawiło mnie na dobrej drodze, więc ją zaakceptowałem. Kończy się to bardziej jak połączoną listą, przy czym każda operacja ma odniesienie do następującej operacji, jak wyjaśniono w tym poście na blogu: http://seewah.blogspot.sg/2009/02/gwt-tips-1-chain- of-responsibility.html (w moim przypadku kod był bardziej skomplikowany, ponieważ istniało wiele typów żądań i odpowiedzi). –

2

Można użyć modelu komputerowego aktor. W twoim przypadku klient, usługi i oddzwanianie [B-D] wszyscy mogą być reprezentowani jako aktorzy.

Istnieje wiele bibliotek aktorów dla java. Większość z nich jest jednak waga ciężka, dlatego napisałem wersję kompaktową i rozszerzalną: df4j. Uważa model aktora za konkretny przypadek bardziej ogólnego modelu obliczania przepływów danych, w wyniku czego użytkownik może tworzyć nowe typy aktorów, aby optymalnie dopasować wymagania użytkownika.

+0

Wydajesz się być autorem df4j, w takim przypadku myślę, że powinieneś to ujawnić, gdy to polecisz. –

+1

Tak, jestem autorem df4j. Odpowiedź zaktualizowana. –

+0

Czy są jakieś przykłady użycia df4j do rozwiązania w szczególności zagnieżdżonego problemu oddzwaniania? –

6

Moim zdaniem najbardziej naturalnym sposobem modelowania tego rodzaju problemów jest Future<V>.

Zamiast zwracać się do oddzwonienia, po prostu zwróć "thunk": Future<Response>, który reprezentuje odpowiedź, która będzie dostępna w pewnym momencie w przyszłości.

Następnie można albo modelować kolejne kroki, takie jak Future<ResponseB> step2(Future<ResponseA>), albo użyć ListenableFuture<V> z Guava. Następnie możesz użyć Futures.transform() lub jednego z jego przeciążeń, aby łączyć swoje funkcje w naturalny sposób, ale jednocześnie zachowując asynchroniczną naturę.

Jeśli jest używany w ten sposób, Future<V> zachowuje się jak monada (w rzeczywistości, myślę, że może się kwalifikować jako jedna, chociaż nie jestem pewna z góry mojej głowy), a więc cały proces wydaje się trochę jak IO w Haskell wykonanym za pośrednictwem monady IO.

+0

Jednym z problemów j.u.c.Future jest to, że Future.get() nie może być wywołany z zadania, które wykonuje pod j.u.c.Executor (może powodować zakleszczenie), tylko z oddzielnego wątku. –

+0

@AlexeiKaigorodov: Myślę, że jest to bezpieczne, o ile każde zadanie ma swój własny "Executor" (co w zasadzie jest tym, co powiedziałeś).Jest to prawdopodobnie najlepszy powód, aby użyć 'ListenableFuture ': aby ochronić cię przed blokowaniem połączenia z 'Future.get()'. –

1

Nie jestem pewien, czy prawidłowo podam pytanie. Jeśli chcesz wywołać usługę, a jej zakończenie, wynik musi zostać przekazany do innego obiektu, który może kontynuować przetwarzanie przy użyciu wyniku.Możesz wykorzystać Composite i Observer, aby to osiągnąć.

Powiązane problemy