
当今许多人喜欢反应式编程。 它具有很多优点:缺少所谓的“
回调地狱 ”,内置的错误处理机制以及可减少错误可能性的实用编程风格。 明显更容易编写多线程代码,也更容易管理数据流(组合,拆分和转换)。
许多编程语言都有自己的反应式库:用于JVM的RxJava,用于JavaScript的RxJS,用于iOS的RxSwift,Rx.NET等。
但是我们对Kotlin有什么呢? 假设RxKotlin是合乎逻辑的。 而且确实存在这样的库,但是它只是RxJava2(所谓的“糖”)的一组扩展。
理想情况下,我希望有一个满足以下条件的解决方案:
- 多平台-能够使用反应式编程编写多平台库并在公司内部分发;
- 空安全性 -Kotlin类型系统保护我们免受“ 十亿美元错误 ”的影响,因此空值必须有效(例如,
Observable<String?>
);
- 协方差和相反方差是Kotlin的另一个非常有用的功能,例如,可以将类型
Observable<String>
安全地Observable<String>
为Observable<CharSequence>
。
我们在Badoo决定不等待海边的天气,并建立了这样的图书馆。 您可能已经猜到了,我们将其称为Reaktive并将其发布在
GitHub上 。
在本文中,我们将仔细研究Kotlin的反应式编程期望,并了解Reaktive的功能如何与之匹配。
三大自然优势
多平台
第一个
自然优势是最重要的。 我们的iOS,Android和移动网络团队目前分别存在。 要求是一般的,设计是相同的,但是每个团队都做自己的工作。
Kotlin允许您编写多平台代码,但您必须忘记反应式编程。 而且我希望能够使用反应式编程来编写共享库,并在公司内部分发它们或将其上传到GitHub。 这种方法可能会大大减少开发时间并减少代码总量。
空安全
而是关于Java和RxJava2的缺陷。 简而言之,不能使用null。 让我们尝试找出原因。 看一下这个Java接口:
public interface UserDataSource { Single<User> load(); }
结果可以为空吗? 为避免歧义,RxJava2中不允许null。 如果您仍然需要,那就是Maybe和Optional。 但是在科特林没有这样的问题。 我们可以说
Single<User>
和
Single<User?>
不同的类型,所有问题在编译阶段都会弹出。
协方差和自变量
这是Kotlin的独特功能,而Java却很缺乏此功能。 您可以在
手册中了解更多信息。 我仅给出几个有趣的示例,说明在Kotlin中使用RxJava时出现什么问题。
协方差 :
fun bar(source: Observable<CharSequence>) { } fun foo(source: Observable<String>) { bar(source)
由于
Observable
是Java接口,因此此类代码无法编译。 这是因为Java中的泛型类型是不变的。 您当然可以用完,但是使用诸如scan之类的运算符将再次导致编译错误:
fun bar(source: Observable<out CharSequence>) { source.scan { a, b -> "$a,$b" }
scan语句的不同之处在于其通用类型“ T”既是输入也是输出。 如果Observable是Kotlin接口,则其类型T可以表示为out,这将解决问题:
interface Observable<out T> { … }
这是一个具有相反性的示例:
fun bar(consumer: Consumer<String>) { } fun foo(consumer: Consumer<CharSequence>) { bar(consumer)
出于与上一个示例相同的原因(Java中的通用类型是不变的),该示例无法编译。 添加将解决此问题,但同样不能解决百分之一百:
fun bar(consumer: Consumer<in String>) { if (consumer is Subject) { val value: String = consumer.value
好吧,按照传统,在Kotlin中,可以通过在界面中使用解决此问题:
interface Consumer<in T> { fun accept(value: T) }
因此,泛型类型的可变性和矛盾性是Reaktive库的第三个
自然优势。
Kotlin +反应性=反应性
我们传递给最主要的东西-Reaktive库的描述。
以下是其一些功能:
- 它是多平台的,这意味着您最终可以编写通用代码。 在Badoo,我们认为这是最重要的好处之一。
- 它是用Kotlin编写的,它为我们提供了上述优点:对null,方差/对数没有限制。 这增加了灵活性并在编译期间提供了安全性。
- 不依赖于其他库,例如RxJava,RxSwift等,这意味着无需将库功能带到一个共同的分母。
- 纯API。 例如,Reaktive中的
ObservableSource
接口简称为Observable
,并且所有运算符都是位于单独文件中的扩展功能。 没有15,000行的上帝课。 这样就可以轻松增加功能,而无需更改现有接口和类。
- 支持调度程序(使用熟悉的
subscribeOn
和observeOn
)。
- 与RxJava2兼容(互操作性),提供Reaktive和RxJava2之间的源转换,并能够重用RxJava2中的调度程序。
- ReactiveX合规性。
由于库是用Kotlin编写的,因此我想谈一谈更多有关我们所获得的好处。
- 在Reaktive中,允许使用空值,因为在Kotlin中它是安全的。 以下是一些有趣的示例:
observableOf<String>(null) //
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = o1 // ,
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = o1.notNull() // , null
val o1: Observable<String> = observableOf("Hello")
val o2: Observable<String?> = o1 //
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = observableOf("Hello")
val o3: Observable<String?> = merge(o1, o2) //
val o4: Observable<String> = merge(o1, o2) // ,
变异也是一大优势。 例如,在Observable
接口中,类型T被声明为out
,这使得可以编写如下内容:
fun foo() { val source: Observable<String> = observableOf("Hello") bar(source)
这是今天图书馆的样子:- 撰写本文时的状态:alpha(可能会更改公共API);
- 支持的平台:JVM和Android;
- 支持的来源:
Observable
, Maybe
, Single
和Completable
;
- 支持相当多的运算符,包括地图,过滤器,flatMap,concatMap,combinateLatest,zip,merge和其他(完整列表可在GitHub上找到 );
- 支持以下调度程序:计算,IO,蹦床和主程序;
- 主题:PublishSubject和BehaviorSubject;
- 尚不支持背压,但我们正在考虑此功能的必要性和实现。
我们对近期的计划是什么:- 开始在我们的产品中使用Reaktive(我们正在考虑选择);
- JavaScript支持(请求已在审核中);
- iOS支持
- 在JCenter中发布工件(当前使用JitPack服务);
- 文献资料
- 支持的运营商数量增加;
- 测验
- 更多平台-欢迎提出要求!
您现在可以尝试使用该库,可以在
GitHub上找到所需的一切。 分享您的经验并提出问题。 我们将感谢您的任何反馈。