函数式功能
可重用函数块基本是你一开始使用 Reactor 就需要的核心功能。[1] 那么函数式编程酷在哪里呢? 其核心理念之一将可执行代码当作另一种数据来处理。[2]业务逻辑由原始调用者决定,这与闭包和匿名函数的理念不谋而合。函数式编程还避免了 IF/SWITCH 语句块的包袱,并清晰地分离了功能:每个代码块只负责一个独立功能,而不共享内容。
除非你只想用核心处理功能,而这些功能在这一阶段是基本独立的。我们打算逐步将调度器与核心调整到一致。
有人也许要说这观点过于简化了,不过我们这里先讲求实用 :)
规划函数块
每个函数组件有明确的功能:
消费者:使用回调函数 — 登记后就不用管了
双向消费者:带双参数的简单回调 (通常用于序列比较,如前后参数比较)
函数:转换逻辑 - 请求/回应
双向函数:带双参数的转换逻辑 (通常用于累加器,比较前后参数并返回一个新值)
供给者:工厂逻辑 - 轮询
谓词:测试逻辑 - 过滤
¡ 我们将发布者和订阅者接口也作为函数块处理,我们称之为响应式函数块。它们是基本的组件,在 Reactor 和 Beyond 中到处都有用到。通常可以直接调用数据流 API 来创建恰当的订阅者,你只需要向 API 传入 reactor.fn 参数。
好消息是:封装在函数功能中的可执行指令,可以像乐高积木一样重用。
Consumer<String> consumer = new Consumer<String>(){
@Override
void accept(String value){
System.out.println(value);
}
};
// 为了简约,现在用 Java 8 风格
Function<Integer, String> transformation = integer -> ""+integer;
Supplier<Integer> supplier = () -> 123;
BiConsumer<Consumer<String>, String> biConsumer = (callback, value) -> {
for(int i = 0; i < 10; i++){
// 对要运行的最后逻辑运行做惰性求值
callback.accept(value);
}
};
// 注意生产者到双向消费者执行过程
biConsumer.accept(
consumer,
transformation.apply(
supplier.get()
)
);
乍一看,你可能会觉得这个革新并不特别,但是这种编程理念的变化,对后续我们构建分层可组合代码却尤其重要。调度者通过消费者处理类型化的数据和错误的回调。Reactor Stream 模块也基于该理念实现优雅编码。
♠ 使用 Spring 这样的 IoC 容器的良好实践是利用 Java 配置特性返回无状态函数式 Beans。然后就可以从容地将代码块注入数据流管道,或者指派代码块的执行。
元组
或许你已经注意到:Reactor 提供的接口都是强类型、带有泛型支持和少量确定数目的参数。那如果形参个数大于1或者2呢,又该怎么办呢?此时,需要使用一个类:元组。元组像是单对象实例中的带类型 CSV 行,在函数式编程中,就是通过元组保证类型安全呢和可变参数。
让我们用双参数双向消费者代替单参数消费者实现上例的过程:
Consumer<Tuple2<Consumer<String>, String>> biConsumer = tuple -> {
for(int i = 0; i < 10; i++){
// 类型正确,开启编译器
tuple.getT1().accept(tuple.getT2());
}
};
biConsumer.accept(
Tuple.of(
consumer,
transformation.apply(supplier.get())
)
);
¡ 元组涉及到更多的资源分配,因此,通常键值对比较和键值信号量更倾向使用 Bi **类型接口。