函数式功能

可重用函数块基本是你一开始使用 Reactor 就需要的核心功能。[1] 那么函数式编程酷在哪里呢? 其核心理念之一将可执行代码当作另一种数据来处理。[2]业务逻辑由原始调用者决定,这与闭包和匿名函数的理念不谋而合。函数式编程还避免了 IF/SWITCH 语句块的包袱,并清晰地分离了功能:每个代码块只负责一个独立功能,而不共享内容。

  1. 除非你只想用核心处理功能,而这些功能在这一阶段是基本独立的。我们打算逐步将调度器与核心调整到一致。

  2. 有人也许要说这观点过于简化了,不过我们这里先讲求实用 :)

规划函数块

每个函数组件有明确的功能:

  • 消费者:使用回调函数 — 登记后就不用管了

  • 双向消费者:带双参数的简单回调 (通常用于序列比较,如前后参数比较)

  • 函数:转换逻辑 - 请求/回应

  • 双向函数:带双参数的转换逻辑 (通常用于累加器,比较前后参数并返回一个新值)

  • 供给者:工厂逻辑 - 轮询

  • 谓词:测试逻辑 - 过滤

¡ 我们将发布者和订阅者接口也作为函数块处理,我们称之为响应式函数块。它们是基本的组件,在 Reactor 和 Beyond 中到处都有用到。通常可以直接调用数据流 API 来创建恰当的订阅者,你只需要向 API 传入 reactor.fn 参数。

好消息是:封装在函数功能中的可执行指令,可以像乐高积木一样重用。

  1. Consumer<String> consumer = new Consumer<String>(){
  2. @Override
  3. void accept(String value){
  4. System.out.println(value);
  5. }
  6. };
  7. // 为了简约,现在用 Java 8 风格
  8. Function<Integer, String> transformation = integer -> ""+integer;
  9. Supplier<Integer> supplier = () -> 123;
  10. BiConsumer<Consumer<String>, String> biConsumer = (callback, value) -> {
  11. for(int i = 0; i < 10; i++){
  12. // 对要运行的最后逻辑运行做惰性求值
  13. callback.accept(value);
  14. }
  15. };
  16. // 注意生产者到双向消费者执行过程
  17. biConsumer.accept(
  18. consumer,
  19. transformation.apply(
  20. supplier.get()
  21. )
  22. );

乍一看,你可能会觉得这个革新并不特别,但是这种编程理念的变化,对后续我们构建分层可组合代码却尤其重要。调度者通过消费者处理类型化的数据和错误的回调。Reactor Stream 模块也基于该理念实现优雅编码。

♠ 使用 Spring 这样的 IoC 容器的良好实践是利用 Java 配置特性返回无状态函数式 Beans。然后就可以从容地将代码块注入数据流管道,或者指派代码块的执行。

元组

或许你已经注意到:Reactor 提供的接口都是强类型、带有泛型支持和少量确定数目的参数。那如果形参个数大于1或者2呢,又该怎么办呢?此时,需要使用一个类:元组。元组像是单对象实例中的带类型 CSV 行,在函数式编程中,就是通过元组保证类型安全呢和可变参数。

让我们用双参数双向消费者代替单参数消费者实现上例的过程:

  1. Consumer<Tuple2<Consumer<String>, String>> biConsumer = tuple -> {
  2. for(int i = 0; i < 10; i++){
  3. // 类型正确,开启编译器
  4. tuple.getT1().accept(tuple.getT2());
  5. }
  6. };
  7. biConsumer.accept(
  8. Tuple.of(
  9. consumer,
  10. transformation.apply(supplier.get())
  11. )
  12. );

¡ 元组涉及到更多的资源分配,因此,通常键值对比较和键值信号量更倾向使用 Bi **类型接口。