Использование значений из ранее прикованных лямбда-выражений thenCompose в Java 8 - PullRequest
0 голосов
/ 05 октября 2018

Стиль программирования Java 8, который предпочитали мои коллеги, - это связывание асинхронных вызовов на всем протяжении, например,

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      // ...
      return b;
    }).thenCompose(b -> {
      // ...
      return c;
    }).thenCompose(c -> {
      // ...
      return d;
    }).thenApply(d -> {
      // ...
      return e;
    });
}

У меня есть что-то подобное выше, но с дополнительной проблемой:Мне нужно вспомнить значения, полученные в некоторых лямбдах, в более поздних лямбдах.Например,

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      // ...
      Foo foo = fooDAO.getFoos(a);
      // ...
      return b;
    }).thenCompose(b -> {
      // ...
      return c;
    }).thenCompose(c -> {
      // ...
      Bar bar = barDAO.getBars(foo);
      // ...
      return d;
    }).thenApply(d -> {
      // ...
      return someResult(d, bar);
    });
}

Когда я объявляю Foo foo; и Bar bar; во внешней области, я получаю ошибки о том, что они не являются окончательными или фактически окончательными.И я читал об использовании обертки, чтобы сделать их эффективно окончательными, но мне кажется довольно глупым сделать это (я не понимаю, почему это разрешено ...)

Я читал, что Java 8 не сделаладобавить поддержку для кортежей (хотя он рассматривал BiVal, который, возможно, я мог бы использовать, чтобы передать BiFunction лямбда-выражения).Поэтому я попытался использовать пары, например

    return doSomething().thenCompose(a -> {
      // ...
      Foo foo = fooDAO.getFoos(a);
      // ...
      return new Pair<>(foo, b);
    }).thenCompose(fooAndB -> {

, а затем везде, где мне нужно было вспомнить foo,

      Foo foo = fooAndB.getKey();

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

Что на самом делеобъем лямбда-параметров, и есть ли идиоматический или, по крайней мере, семантически не оскорбительный способ сделать то, что я хотел бы сделать, сохраняя цепочку?

Ответы, основанные на разрыве цепочки, хороши, как они могутбыть полезным для будущих зрителей, но в моем случае отклонения от доминирующего стиля в этом репо могут привести к затяжным пиар-разговорам и задержке одобрения, поэтому я бы хотел, чтобы решение сохраняло цепочку.Или объяснение или демонстрация того, как безумно было бы пытаться сохранить цепочку.Спасибо!

Ответы [ 3 ]

0 голосов
/ 05 октября 2018

Поскольку вы упоминаете стиль кодирования , предпочитаемый вашими коллегами , вы, вероятно, уже знаете альтернативу использования вместо этого вложенных вызовов:

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
        // ...
        Foo foo = fooDAO.getFoos(a);
        // ...
        CompletableFuture<B> resultB = ...;
        return resultB.thenCompose(b -> {
            // ...
            CompletableFuture<C> resultC = ...;
            return resultC;
        }).thenCompose(c -> {
            // ...
            Bar bar = barDAO.getBars(foo);
            // ...
            CompletableFuture<D> resultD = ...;
            return resultD.thenApply(d -> {
                // ...
                return someResult(d, bar);
            });
        });
    });
}

Это немедленно решает вашу проблему, за счетчуть менее читаемого кода.Но эту проблему легко решить, извлекая некоторые методы из вашего кода:

CompletionStage<E> someMethod() {
    return doSomething()
            .thenCompose(this::processA);
}

private CompletionStage<E> processA(final A a) {
    // ...
    Foo foo = fooDAO.getFoos(a);
    // ...
    final CompletableFuture<B> result = ...;
    return result
            .thenCompose(this::processB)
            .thenCompose(c -> processCAndFoo(c, foo));
}

private CompletionStage<C> processB(B b) {
    // ...
    return ...;
}

private CompletionStage<E> processCAndFoo(final C c, final Foo foo) {
    // ...
    Bar bar = barDAO.getBars(foo);
    // ...
    final CompletableFuture<D> result = ...;
    return result
            .thenApply(d -> someResult(d, bar));
}

Делая это, вы избегаете вложенных лямбд (и уважаете предпочтительный стиль кода ваших коллег), но вы также получаете удобочитаемостьи тестируемость, так как теперь у вас есть несколько небольших методов, которые проще для понимания и модульного тестирования.

0 голосов
/ 05 октября 2018

В качестве другой альтернативы вы можете также рассмотреть возможность использования EA Async в своем проекте.Этот API обеспечивает async / await для Java и особенно удобен для такого рода проблем.Здесь я переписал ваш код с помощью EA Async:

CompletionStage<E> someMethodWithEAAsync() {
    final A a = await(doSomething());
    Foo foo = fooDAO.getFoos(a);
    // ...
    CompletableFuture<B> futureB = ...;
    final B b = await(futureB);
    // ...
    CompletableFuture<C> futureC = ...;
    final C c = await(futureC);
    // ...
    Bar bar = barDAO.getBars(foo);
    // ...
    CompletableFuture<D> futureD = ...;
    D d = await(futureD);
    return completedFuture(someResult(d, bar));
    // or alternatively
    return futureD.thenApply(d -> someResult(d, bar));
}

Он действительно похож на синхронный код, но за кадром EA Async преобразует этот метод, чтобы сохранить все асинхронным.Больше не нужно беспокоиться о цепочке!

0 голосов
/ 05 октября 2018

Вы можете попробовать этот подход (я не обязательно сторонник такого подхода, но он поддерживает цепочку).Вы создаете POJO, который содержит все аргументы, которые вам понадобятся для цепочки, и передаете тот же POJO по цепочке.Недостатком здесь является то, что добавление дополнительных методов в цепочку затруднительно, поскольку теперь вам также необходимо добавить свойство в класс аргумента.

public class SomeMethodContext {

    private Object a;
    private Object b;
    private Object c;
    private Object d;
    private Object foo;
    private Object bar;

    // Getters and setters

}

// ...

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      SomeMethodContext context = new SomeMethodContext();  
      context.setA(a);
      // ...
      context.setFoo(fooDAO.getFoos(context.getA()));
      // ...
      context.setB(b);
      return context;
    }).thenCompose(ctxt -> {
      // ...
      ctxt.setC(c);
      return ctxt;
    }).thenCompose(ctxt -> {
      // ...
      ctxt.setBar(barDAO.getBars(ctxt.getFoo()))
      // ...
      ctxt.setD(d)
      return ctxt;
    }).thenApply(ctxt -> {
      // ...
      return someResult(ctxt.getD(), ctxt.getBar());
    });
}
...