到目前为止,咱们已经学会了如何建立可观测数据 observables ,以及如何从可观测数据中提取相关数据。在本章中,咱们将超越简单示例所必需的内容,讨论更高级的功能,以及在更大的应用程序中使用Rx的一些良好实践。ios
没反作用的函数只经过它们的参数和返回值与程序的其他部分交互。当一个函数内的操做可能影响另外一个函数的结果(或对同一函数的后续调用)时,咱们说该函数有反作用。常见的反作用是写入存储、日志记录、调试或打印到用户界面。一种更依赖于语言的反作用是可以修改对其余函数可见的对象的状态,Java认为这是合法的。做为参数传递给Rx操做符的函数能够修改更大范围内的值、执行IO操做或更新显示。git
反作用可能很是有用,在许多状况下是不可避免的。但它们也有陷阱。鼓励RX开发人员避免没必要要的反作用,并在使用时有明确的意图。虽然有些状况是合理的,但滥用会带来没必要要的危险。程序员
函数式编程一般试图避免产生任何反作用。有反作用的函数,特别是修改状态的函数,要求程序员不只仅理解函数的输入和输出。他们须要理解的表面积如今须要扩展到被修改状态的历史和上下文。这会大大增长函数的复杂性,从而使其更难正确理解和维护。反作用不必定是偶然的,也不必定是有意的。减小意外反作用的一个简单方法是减少改变的表面积。编码人员能够采起的简单操做是下降状态的可见性或范围,并使您能够不变的内容。经过将变量的做用域限定为代码块(如方法),能够下降变量的可见性。经过将类成员设置为私有或受保护,能够下降类成员的可见性。根据定义,不可变数据不能被修改,所以不能显示反作用。这些是合理的封装规则,将大大提升Rx代码的可维护性。github
咱们从一个有反作用的实现示例开始。Java不容许从lambdas(或通常的匿名实现)引用非终结变量。可是,Java中的Final关键字只保护引用,而不保护被引用对象的状态。没有什么能够阻止您修改lambda中对象的状态。考虑这个简单的计数器,它被实现为一个对象,而不是一个基本的int。编程
INC的实例能够修改其状态,即便它被声明为FINAL。咱们将用它来索引可观察到的项目。请注意,虽然Java并无强制咱们将其显式声明为Final,可是若是咱们试图在lambda中使用引用的同时更改引用,则会产生错误。安全
输出:ide
目前看来还好。让咱们看看当咱们尝试订阅第二次可观察到的内容时会发生什么。函数式编程
输出:函数
第二个订阅者看到索引从5开始,这是没有意义的。虽然这里的bug是直截了当地发现的,但反作用可能会致使bug,而bug要微妙得多。编码
在Rx中使用状态的最安全方法是将其包含在发出的数据中。咱们可使用扫描将项目与其索引进行配对。
输出:
如今的结果是有效的。咱们删除了两个订阅之间的共享状态,如今它们不能相互影响了。
在某些状况下,咱们确实但愿出现反作用,例如在日志记录时。订阅方法老是有反作用,不然就没有用了。咱们能够将日志记录放在订阅者的主体中,但这样会有两个缺点:
1.咱们将日志记录的不太有趣的代码与订阅的关键代码混合在一块儿。
2.若是咱们想要在咱们的管道中记录一个中间状态,例如映射以前和以后,咱们将不得不为此引入一个额外的订阅,它不必定可以准确地看到消费者看到的内容以及他们看到它的时间。
下一组方法帮助咱们以更整洁的方式声明反作用:
如咱们所见,它们在发出项时执行操做。它们还返回可观察的<T>,这意味着咱们能够在管道中的运算符之间使用它们。在某些状况下,您可使用map或筛选器实现相同的结果。使用Doon*更好,由于它记录了您产生反作用的意图。下面是一个例子:
输出:
咱们重用了前面章节中的方便的PrintSubcriber。“do”方法不受稍后管道中的转换的影响。不管消费者实际消费什么,咱们均可以记录服务产生的内容。考虑如下服务:
输出:
咱们记录了服务产生的全部内容,即便使用者修改并过滤告终果。
此时,“do”的不一样变体之间的差别应该是显而易见的。总之:
doOnEach
当发出任何通知时运行doOnNext
当值发出时运行doOnError
当可观察到的终止出现错误时运行doOnCompleted
当可观察到的终止没有错误时运行doOnTerminate
当可观察到的终止时运行一个特别的注意是onTerminate,它正好在可观察到的终止以前以onCompleted或onError结束。还有一个方法finallyDo,它将在可观察到的终止以后当即运行。
订阅和取消订阅不是可观察到的事件发出的事件。它们仍然能够被看做是通常意义上的事件,当它们发生时,您可能但愿执行一些操做。最有可能的状况是,您将使用它们进行日志记录。
输出:
RX是以函数式编程的方式设计的,可是它存在于一个面向对象的环境中.。咱们还必须防范面向对象的危险。对于返回可观察到的服务,请考虑这种天真的实现。
上面的代码并不阻止淘气的消费者使用他们本身的项来更改您的项。在此以后,在更改以前完成的订阅将再也不接收项,由于您再也不对正确的主题调用onNext。很明显咱们须要隐藏对目标的访问
如今,咱们的引用是安全的,但咱们仍然公开一个主题的引用。任何人均可以在Next上调用咱们的主题,并在咱们的序列中注入值。咱们应该只返回可观察的<T>,它是一个不可变的对象。被试扩展可观察性,咱们能够投射咱们的对象
咱们的API如今看起来很安全,但事实并不是如此。没有什么能够阻止用户发现咱们的可观察对象其实是一个Subject(例如,使用instanceof),将它转换为一个 Subject,并像之前同样使用它。
“可观察”方法背后的思想是将“可观察”的扩展包装成能够安全共享的实际“可观察”,由于“可观察”是不可变的。
如今咱们已经很好地保护了咱们的目标。这种保护措施不只能够防止恶意攻击,并且还能够防止错误。咱们在前面已经提到,当存在替代方案时,应该避免使用这些主题,如今咱们已经看到了为何要这样作的例子。被试向咱们的观察对象介绍状态。对onNext、onCompleted和onError的调用会改变使用者将看到的顺序。只要咱们本身不产生反作用,就像咱们在有反作用的问题上看到的那样,用可观察到的任何工厂方法或操做人员构建的可观测性是不可变的。
正如人们可能指望的那样,Rx管道将引用转发到对象,而不建立副本(除非咱们在提供的函数中本身建立副本)。对对象的修改对使用它们的管道中的每一个位置都是可见的。考虑如下可变类:
如今,咱们展现了一个可观察到的类型和两个订阅。
输出:
第一个订阅者首先被每一个项目调用,它的做用是修改数据。一旦第一个订阅者完成,一样的引用也被传递给第二个订阅者,只是如今数据以一种在生产者中没有声明的方式被改变。开发人员须要对Rx、Java及其环境有深入的理解,以便对修改的顺序进行推理,而后论证这样的代码将按照计划运行。更简单的方法是彻底避免可变状态。可观察性应视为已解决事件的序列通知。
原文连接:
https://github.com/Froussios/Intro-To-RxJava/blob/master/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md