【问题标题】:Chain calls inifnitely无限调用链
【发布时间】:2017-09-16 02:36:37
【问题描述】:

我有一个需要输入并返回输出的服务:

Item callService(Item input);

输入始终是可观察对象发出的前一个元素,服务调用的输出应该是可观察对象发出的下一个元素。并且应该重复这个序列,直到一个项目满足某个条件。

实际上,我有一片树叶,我将树叶发送到一个服务,该服务告诉我它的父级是什么。我会继续这样做,直到找到根。

我的第一个解决方案:

我递归调用一个 flatMap 函数。由于递归,我不喜欢这个解决方案:

Observable.just(Item.of("start"))
          .map(Result::of)
          .flatMap(App::fallbackUntilIsRoot)
          .blockingSingle();

private static Observable<Result> fallbackUntilIsRoot(Result previousResult)
{
    if (previousResult.isRoot())
    {
        return Observable.just(previousResult);
    } else
    {
        return Service.call(previousResult.getItem())
                      .flatMap(App::fallbackUntilIsRoot); // recursion
    }
}

我的第二个解决方案:

我维护一个外部队列,基本上我用这个队列提供 observable。看起来还是不太好。

Queue<Item> todoList = new LinkedList<>(Collections.singleton(Item.of("start")));

Result result = Observable.range(1, Integer.MAX_VALUE) //max tries
                          .concatMap(attempt -> Service.call(todoList))
                          .takeUntil(Result::isEmpty)
                          .takeLast(2)
                          .firstElement()
                          .blockingGet();

public class Service
{
    private static final Queue<Item> itemsProvidedByService = new LinkedList<>(
            Arrays.asList(Item.of("first"), Item.of("second"), Item.of("third"), Item.of("fourth"), Item.of("root")));

    public static Observable<Result> call(Queue<Item> todoList)
    {
        Item previousItem = todoList.poll();

        Item newItem = itemsProvidedByService.poll(); // we could pass previousItem to a real service here

        if (newItem == null)
        {
            return Observable.just(Result.empty());
        }

        todoList.add(newItem);

        return Observable.just(Result.of(newItem));
    }
}

有没有一种 Rx 方式来进行这种链接?

【问题讨论】:

    标签: java rx-java


    【解决方案1】:

    与此同时,我找到了我的问题的名称。它被称为核心递归。 RxJava 2 为这个问题提供了一个干净(无副作用)的解决方案。

    private static void solutionWithGenerate()
    {
        Result startElement = Result.of(Item.of("start"));
    
        Result result = Observable.generate(() -> startElement, App::generate)
                                  .startWith(startElement)
                                  .lastElement()
                                  .blockingGet();
    
        System.out.println(result);
    }
    
    private static Result generate(Result previousResult, Emitter<Result> emitter)
    {
        Result newResult = Service.callDirectly(previousResult.getItem());
    
        if (newResult.isEmpty())
        {
            emitter.onComplete();
        } else
        {
            emitter.onNext(newResult);
        }
    
        return newResult;
    }
    

    也可以在 RxJava 1 中完成。更详细一点: Observable.Generate in RxJava?

    【讨论】:

      【解决方案2】:

      您可以通过保留当前迭代项的单个副作用字段来做到这一点。
      repeat()takeUntil() 组合在一起将使while 循环与fromCallable() 一起使迭代器在循环中可变(即使用just() 将永远重复初始值):

      it = Result.of(Item.of("start"));
      Observable.fromCallable(() -> it)
              .map(result -> {
                  it = service.call(result);
                  return result;
              })
              .repeat()
              .takeUntil(Item -> it.isRoot())
              .subscribe(item -> .....);
      

      【讨论】:

      • 在java中它不起作用,因为lambda中的变量必须是最终的或有效的最终,所以我仍然需要使用某种数据结构来跟踪当前元素。虽然对于我的用例来说,重复似乎比我的解决方案更有效且更具表现力,但副作用仍然困扰着我。
      • 是的,正如我所说,它需要你所说的单一副作用字段或数据结构。
      猜你喜欢
      • 1970-01-01
      • 2020-12-13
      • 2016-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 2022-01-06
      相关资源
      最近更新 更多