我正在尝试通过重构一些当前阻塞的代码来学习反应式编程。我多次遇到过这样的问题,即从Mono
序列中设置某些可变数据对象的状态而不订阅它。在旧代码中,对象的字段值是由某些阻塞服务计算的,我现在也在Mono
s内部进行了
到目前为止,我通常一直在(ab)使用flatMap
来获得预期的行为:
initExpensiveObject().flatMap(expObj -> initExpensiveField(expObj).map(expField -> {
expObj.setExpensiveField(expField);
return expObj;
})).subscribe(expObj -> System.out.println("expensiveField: " + expObj.getExpensiveField()));
import reactor.core.publisher.Mono;
public class Main {
/**
* Expensive, lazy object instantiation
*/
public static Mono<ExpensiveObject> initExpensiveObject() {
return Mono.fromCallable(ExpensiveObject::new);
}
/**
* Expensive, async mapping (i.e. database access, network request):
* ExpensiveObject -> int
*/
public static Mono<Integer> initExpensiveField(ExpensiveObject expObj) {
return Mono.just(1);
}
public static class ExpensiveObject {
private int expensiveField = -1;
public int getExpensiveField() {
return expensiveField;
}
public void setExpensiveField(int expensiveField) {
this.expensiveField = expensiveField;
}
}
}
虽然这种flatMap
模式有效,但我认为应该有一个更具响应性的解决方案。考虑到Mono
中仅存在如此多的运算符,从直观上感觉将一个对象“映射”到同一对象以改变其状态是错误的。但是,“副作用”运算符(doOn*
)不允许在不订阅的情况下轻松转换其他发布者。
如果对我的问题没有简单的解决方案,我非常愿意进行设计改进,因为代码的设计仍然是 sequential 。
答案 0 :(得分:1)
虽然这个flatMap模式有效,但我认为应该有一个更具响应性的解决方案。
可能不是您想听到的答案,但被动的解决方案是完全放弃变异性。在更复杂的示例中,将可变对象传递到反应链中会导致意外的副作用,这可能会导致某些相当难以跟踪的错误。完全重构可变性要容易得多。
如果没有解决问题的简单方法,我非常愿意进行设计改进
我将采用的“最小变化”方法是:
ExpensiveObject
不可变。删除setter方法,并提供另一个为expensiveField
带有显式值的构造函数。withExpensiveField()
上提供“反应式” ofExpensiveField()
(或ExpensiveObject
,或完全由您选择!)的方法,该方法需要{{1 }},并返回Mono<Integer>
。expensiveField
调用来构建反应链,并且看不到任何可变对象:
Mono<ExpensiveObject>
上面有修改的代码:
flatMap()
您可能要根据最终设计来更改上述内容(例如,initExpensiveObject()
.flatMap(expObj -> expObj.withExpensiveField(initExpensiveField(expObj)))
.subscribe(expObj -> System.out.println("expensiveField: " + expObj.getExpensiveField()));
方法对单个字段对象没有多大意义),但这涉及了主要思想。