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

anonymous classes: gets ugly quickly because of the required nesting inner classes: neater than the above, but still hard for another developer to comprehend the flow of execution

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

 fifth02 сент. 2014 г., 15:51
Будет лиRxJava Помогите?
 Andrew Swan22 мая 2012 г., 05:14
@Rob I: к сожалению, неблокирование является обязательным.
 Rob I22 мая 2012 г., 04:56
Я знаю, что вы сказали, что неблокирование было необходимостью, но есть ли способ пересмотреть это? Например, не могли бы вы создать второй поток, блокирующий каждый запрос? Я мог видеть, что код был очень ясным с осторожностью.
 Pavel Veller22 мая 2012 г., 05:10
Автономные операции все еще будут в некоторой форме классов, анонимных или нет, но все же физически присутствовать. придется ждать, пока проект лямбда получит более естественные конструкции для таких вещей, как вы. Я также хотел бы предположить, что вам нужно все это в рамках "обычной Java" парадигм? Не похоже, что вы хотели бы иметь внешнюю структуру оркестровки
 Rob I22 мая 2012 г., 05:23
@AndrewSwan, так как реализация (не только интерфейс) не должна блокироваться, мне нравится ваша идея списка - настройте список & quot; операций & quot; (возможно,Futures?), для которого установка должна быть довольно понятной. Затем при получении каждого ответа должна быть вызвана следующая операция. С небольшим воображением это звучит какchain of responsibility.

Ответы на вопрос(4)

Вы можете использовать модель вычисления актера. В вашем случае клиент, сервисы и обратные вызовы [B-D] могут быть представлены как акторы.

Есть много библиотек актеров для Java. Большинство из них, однако, тяжелые, поэтому я написал компактный и расширяемый:df4j, Он рассматривает модель актора как особый случай более общей вычислительной модели потока данных и, как результат, позволяет пользователю создавать новые типы акторов, чтобы оптимально соответствовать требованиям пользователя.

 Andrew Swan23 мая 2012 г., 04:42
Есть ли какие-либо примеры использования df4j для решения проблемы вложенного обратного вызова, в частности?
 Andrew Swan22 мая 2012 г., 10:45
Вы, кажется, автор df4j, и в этом случае я думаю, что вы должны сообщить об этом, когда порекомендуете его.
 23 мая 2012 г., 21:38
Я не рассматриваю «проблему вложенного обратного вызова» как проблема, поскольку, как только обратные вызовы представляются как акторы, вложение исчезает. У df4j есть много примеров использования актеров. Актеры - это объекты, которые работают асинхронно. Ссылки на актеров могут быть отправлены в сообщениях, так что субъект, который обрабатывает сообщение, может получить доступ к указанному субъекту. Идея df4j заключается в том, что пользователь может расширить его, чтобы он соответствовал конкретному случаю использования (скажем, создайте актероподобный класс, который реализует Callback & lt; R extends Response & gt; и другой класс с методом send (Request, Callback)).
 22 мая 2012 г., 17:30
Да, я автор df4j. Ответ обновлен.

На мой взгляд, наиболее естественным способом моделирования такого рода проблем являетсяFuture<V>.

Таким образом, вместо использования обратного вызова, просто верните & quot; thunk & quot ;: aFuture<Response> это представляет ответ, который будет доступен в некоторый момент в будущем.

Затем вы можете моделировать последующие шаги какFuture<ResponseB> step2(Future<ResponseA>)или используйтеListenableFuture<V> из гуавы. Тогда вы можете использоватьFutures.transform() или одна из ее перегрузок, чтобы естественным образом связать ваши функции, но при этом сохраняя асинхронную природу.

Если используется таким образом,Future<V> ведет себя как монада (на самом деле, я думаю, что она может быть квалифицирована как единая, хотя я не совсем уверен в этом), и поэтому весь процесс выглядит как IO в Haskell, как выполняется через монаду IO.

 24 мая 2012 г., 00:07
@AlexeiKaigorodov: я думаю, это безопасно, если у каждой задачи есть своиExecutor (что в основном то, что вы сказали). Это, вероятно, лучшая причина для использованияListenableFuture<V>: чтобы защитить вас от блокировки на вызовFuture.get().
 23 мая 2012 г., 21:45
Одна из проблем j.u.c.uture & lt; V & gt; является то, что Future.get () не может быть вызван из задачи, которая выполняется под j.u.c.Executor (может вызвать взаимоблокировку), только из отдельного потока.

правильно ли я задаю вам вопрос. Если вы хотите вызвать сервис и по его завершению результат нужно передать другому объекту, который может продолжить обработку, используя результат. Вы можете посмотреть на использование Composite и Observer для достижения этой цели.

Решение Вопроса

Поскольку реализация (не только интерфейс) не должна блокироваться, мне нравится ваша идея списка.

Составьте список «операций» (возможно,Futures?), для которого установка должна быть довольно понятной и читаемой. Затем при получении каждого ответа должна быть вызвана следующая операция.

С небольшим воображением это звучит какцепь ответственности, Вот некоторый псевдокод для того, что я себе представляю:

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();
    }
}
...
 Andrew Swan15 июн. 2012 г., 02:58
Это поставило меня на правильный путь, поэтому я принял это. В конечном итоге он больше походил на связанный список, где каждая операция имела ссылку на следующую операцию, как описано в этом сообщении в блоге:seewah.blogspot.sg/2009/02/… (в моем случае код был более сложным, потому что было несколько типов запросов и ответов).

Ваш ответ на вопрос