Подтвердить что ты не робот

Шаблон 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:

  • анонимные классы: быстро становится уродливым из-за требуемой вложенности
  • внутренние классы: более аккуратные, чем предыдущие, но все еще трудно для другого разработчика понять поток выполнения

Есть ли какой-нибудь шаблон, чтобы сделать этот код более удобочитаемым? Например, могу ли я выразить метод службы как список автономных операций, которые выполняются последовательно некоторым классом среды, который заботится о вложенности?

4b9b3361

Ответ 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 для достижения этого.