Шаблон Java для вложенных обратных вызовов?
Я ищу шаблон Java для создания вложенной последовательности вызовов неблокирующих методов. В моем случае некоторый код клиента должен асинхронно вызывать службу для выполнения некоторого прецедента, и каждый шаг этого случая использования должен выполняться асинхронно (по причинам, выходящим за рамки этого вопроса). Представьте, что у меня есть существующие интерфейсы:
public interface Request {}
public interface Response {}
public interface Callback<R extends Response> {
void onSuccess(R response);
void onError(Exception e);
}
Существуют различные парные реализации интерфейсов Request
и Response
, а именно RequestA
+ ResponseA
(заданные клиентом), RequestB
+ ResponseB
(используемые службой внутри) и т.д..
Процесс обработки выглядит следующим образом:
![Sequence diagram showing nested callbacks.]()
Между получением каждого ответа и отправкой следующего запроса необходимо выполнить некоторую дополнительную обработку (например, на основе значений в любом из предыдущих запросов или ответов).
До сих пор я пробовал два подхода к кодированию этого в Java:
- анонимные классы: быстро становится уродливым из-за требуемой вложенности
- внутренние классы: более аккуратные, чем предыдущие, но все еще трудно для другого разработчика понять поток выполнения
Есть ли какой-нибудь шаблон, чтобы сделать этот код более удобочитаемым? Например, могу ли я выразить метод службы как список автономных операций, которые выполняются последовательно некоторым классом среды, который заботится о вложенности?
Ответы
Ответ 1
Поскольку реализация (а не только интерфейс) не должна блокироваться, мне нравится ваша идея списка.
Настройте список "операций" (возможно, Future
s?), для которых настройка должна быть довольно понятной и читаемой. Затем, после получения каждого ответа, следует вызвать следующую операцию.
С небольшим воображением это звучит как цепочка ответственности . Вот какой-то псевдокод для того, что я себе представляю:
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();
}
}
...
Ответ 2
На мой взгляд, наиболее естественным способом моделирования такой проблемы является Future<V>
.
Поэтому вместо того, чтобы использовать обратный вызов, просто верните "thunk": a Future<Response>
, который представляет ответ, который будет доступен в какой-то момент в будущем.
Затем вы можете либо моделировать последующие шаги, как вещи, такие как Future<ResponseB> step2(Future<ResponseA>)
, либо использовать ListenableFuture<V>
из Guava. Затем вы можете использовать Futures.transform()
или одну из своих перегрузок, чтобы упорядочить ваши функции естественным образом, но при этом сохраняя асинхронный характер.
Если используется таким образом, Future<V>
ведет себя как монада (на самом деле, я думаю, что он может считаться одним из них, хотя я не уверен в своей голове), и поэтому весь процесс немного как IO в Haskell, как выполняется через монаду IO.
Ответ 3
Вы можете использовать компьютерную модель актера. В вашем случае клиент, службы и обратные вызовы [B-D] могут быть представлены как участники.
Существует много библиотек для java. Большинство из них, однако, являются тяжеловесами, поэтому я написал компактный и расширяемый: df4j. Он рассматривает модель актера как конкретный случай более общей вычислительной модели потока данных и, в результате, позволяет пользователю создавать новые типы участников, чтобы оптимально соответствовать требованиям пользователей.
Ответ 4
Я не уверен, правильно ли я задал вам вопрос. Если вы хотите вызвать службу и ее результат завершения должен быть передан другому объекту, который может продолжить обработку с использованием результата. Вы можете использовать Composite и Observer для достижения этого.