RxJava 学习笔记<九> 译 Taming the sequence

 Taming the sequence

到目前为止,我们已经学会了如何创建可观测数据 observables ,以及如何从可观测数据中提取相关数据。在本章中,我们将超越简单示例所必需的内容,讨论更高级的功能,以及在更大的应用程序中使用Rx的一些良好实践。

Side effects

没副作用的函数只通过它们的参数和返回值与程序的其余部分交互。当一个函数内的操作可能影响另一个函数的结果(或对同一函数的后续调用)时,我们说该函数有副作用。常见的副作用是写入存储、日志记录、调试或打印到用户界面。一种更依赖于语言的副作用是能够修改对其他函数可见的对象的状态,Java认为这是合法的。作为参数传递给Rx操作符的函数可以修改更大范围内的值、执行IO操作或更新显示。

副作用可能非常有用,在许多情况下是不可避免的。但它们也有陷阱。鼓励RX开发人员避免不必要的副作用,并在使用时有明确的意图。虽然有些情况是合理的,但滥用会带来不必要的危险。

Issues with side effects

函数式编程通常试图避免产生任何副作用。有副作用的函数,特别是修改状态的函数,要求程序员不仅仅理解函数的输入和输出。他们需要理解的表面积现在需要扩展到被修改状态的历史和上下文。这会大大增加函数的复杂性,从而使其更难正确理解和维护。副作用不一定是偶然的,也不一定是有意的。减少意外副作用的一个简单方法是减小改变的表面积。编码人员可以采取的简单操作是降低状态的可见性或范围,并使您可以不变的内容。通过将变量的作用域限定为代码块(如方法),可以降低变量的可见性。通过将类成员设置为私有或受保护,可以降低类成员的可见性。根据定义,不可变数据不能被修改,因此不能显示副作用。这些是合理的封装规则,将大大提高Rx代码的可维护性。

我们从一个有副作用的实现示例开始。Java不允许从lambdas(或一般的匿名实现)引用非终结变量。但是,Java中的Final关键字只保护引用,而不保护被引用对象的状态。没有什么可以阻止您修改lambda中对象的状态。考虑这个简单的计数器,它被实现为一个对象,而不是一个基本的int。


INC的实例可以修改其状态,即使它被声明为FINAL。我们将用它来索引可观察到的项目。请注意,虽然Java并没有强制我们将其显式声明为Final,但是如果我们试图在lambda中使用引用的同时更改引用,则会产生错误。

输出:

目前看来还好。让我们看看当我们尝试订阅第二次可观察到的内容时会发生什么。

输出:

第二个订阅者看到索引从5开始,这是没有意义的。虽然这里的bug是直截了当地发现的,但副作用可能会导致bug,而bug要微妙得多。

Composing data in a pipeline

在Rx中使用状态的最安全方法是将其包含在发出的数据中。我们可以使用扫描将项目与其索引进行配对。

输出:

现在的结果是有效的。我们删除了两个订阅之间的共享状态,现在它们不能相互影响了。

do

在某些情况下,我们确实希望出现副作用,例如在日志记录时。订阅方法总是有副作用,否则就没有用了。我们可以将日志记录放在订阅者的主体中,但这样会有两个缺点:

1.我们将日志记录的不太有趣的代码与订阅的关键代码混合在一起。

2.如果我们想要在我们的管道中记录一个中间状态,例如映射之前和之后,我们将不得不为此引入一个额外的订阅,它不一定能够准确地看到消费者看到的内容以及他们看到它的时间。

下一组方法帮助我们以更整洁的方式声明副作用:

如我们所见,它们在发出项时执行操作。它们还返回可观察的<T>,这意味着我们可以在管道中的运算符之间使用它们。在某些情况下,您可以使用map或筛选器实现相同的结果。使用Doon*更好,因为它记录了您产生副作用的意图。下面是一个例子:

输出:

我们重用了前面章节中的方便的PrintSubcriber。“do”方法不受稍后管道中的转换的影响。无论消费者实际消费什么,我们都可以记录服务产生的内容。考虑以下服务:

输出:

我们记录了服务产生的所有内容,即使使用者修改并过滤了结果。

此时,“do”的不同变体之间的差异应该是显而易见的。总之:

  • doOnEach 当发出任何通知时运行
  • doOnNext 当值发出时运行
  • doOnError 当可观察到的终止出现错误时运行
  • doOnCompleted 当可观察到的终止没有错误时运行
  • doOnTerminate 当可观察到的终止时运行

一个特别的注意是onTerminate,它正好在可观察到的终止之前以onCompleted或onError结束。还有一个方法finallyDo,它将在可观察到的终止之后立即运行。

doOnSubscribe, doOnUnsubscribe

订阅和取消订阅不是可观察到的事件发出的事件。它们仍然可以被看作是一般意义上的事件,当它们发生时,您可能希望执行一些操作。最有可能的情况是,您将使用它们进行日志记录。

输出:

Encapsulating with AsObservable

RX是以函数式编程的方式设计的,但是它存在于一个面向对象的环境中.。我们还必须防范面向对象的危险。对于返回可观察到的服务,请考虑这种天真的实现。

上面的代码并不阻止淘气的消费者使用他们自己的项来更改您的项。在此之后,在更改之前完成的订阅将不再接收项,因为您不再对正确的主题调用onNext。很明显我们需要隐藏对目标的访问

现在,我们的引用是安全的,但我们仍然公开一个主题的引用。任何人都可以在Next上调用我们的主题,并在我们的序列中注入值。我们应该只返回可观察的<T>,它是一个不可变的对象。被试扩展可观察性,我们可以投射我们的对象

我们的API现在看起来很安全,但事实并非如此。没有什么可以阻止用户发现我们的可观察对象实际上是一个Subject(例如,使用instanceof),将它转换为一个 Subject,并像以前一样使用它。

asObservable

“可观察”方法背后的思想是将“可观察”的扩展包装成可以安全共享的实际“可观察”,因为“可观察”是不可变的。

现在我们已经很好地保护了我们的目标。这种保护措施不仅可以防止恶意攻击,而且还可以防止错误。我们在前面已经提到,当存在替代方案时,应该避免使用这些主题,现在我们已经看到了为什么要这样做的例子。被试向我们的观察对象介绍状态。对onNext、onCompleted和onError的调用会改变使用者将看到的顺序。只要我们自己不产生副作用,就像我们在有副作用的问题上看到的那样,用可观察到的任何工厂方法或操作人员构建的可观测性是不可变的。

Mutable elements cannot be protected

正如人们可能期望的那样,Rx管道将引用转发到对象,而不创建副本(除非我们在提供的函数中自己创建副本)。对对象的修改对使用它们的管道中的每个位置都是可见的。考虑以下可变类:

现在,我们展示了一个可观察到的类型和两个订阅。

输出:

第一个订阅者首先被每个项目调用,它的作用是修改数据。一旦第一个订阅者完成,同样的引用也被传递给第二个订阅者,只是现在数据以一种在生产者中没有声明的方式被改变。开发人员需要对Rx、Java及其环境有深刻的理解,以便对修改的顺序进行推理,然后论证这样的代码将按照计划运行。更简单的方法是完全避免可变状态。可观察性应视为已解决事件的序列通知。

原文链接:

https://github.com/Froussios/Intro-To-RxJava/blob/master/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md

有什么讨论的内容,可以加我微信公众号:

猜你喜欢

转载自my.oschina.net/u/2277632/blog/1788103