RxJava study notes <9> Translation Taming the sequence

 Taming the sequence

So far, we have learned how to create observables, and how to extract relevant data from observables. In this chapter, we'll go beyond what's necessary for a simple example to discuss more advanced features, as well as some good practices for using Rx in larger applications.

Side effects

Functions without side effects interact with the rest of the program only through their arguments and return values. When an operation within a function may affect the result of another function (or subsequent calls to the same function), we say that the function has side effects. Common side effects are writing to storage, logging, debugging, or printing to the UI. A more language-dependent side effect is the ability to modify the state of objects visible to other functions, which Java considers legal. Functions passed as arguments to the Rx operator can modify a wider range of values, perform IO operations, or update the display.

Side effects can be very useful and are unavoidable in many cases. But they also have pitfalls. RX developers are encouraged to avoid unwanted side effects and use it with clear intent. While some situations are justified, abuse creates unnecessary danger.

Issues with side effects

Functional programming generally tries to avoid any side effects. Functions with side effects, especially those that modify state, require the programmer to understand more than just the input and output of the function. The surface area they need to understand now needs to be extended to the history and context of the modified state. This greatly increases the complexity of the function, making it harder to understand and maintain properly. Side effects are not necessarily accidental or intentional. An easy way to reduce unintended side effects is to reduce the surface area of ​​the change. Simple actions a coder can take is to reduce the visibility or scope of the state and make your content immutable. The visibility of variables can be reduced by scoping them to blocks of code, such as methods. You can reduce the visibility of class members by making them private or protected. By definition, immutable data cannot be modified and therefore cannot exhibit side effects. These are reasonable encapsulation rules that will greatly improve the maintainability of Rx code.

We start with an implementation example with side effects. Java does not allow non-terminal variables to be referenced from lambdas (or anonymous implementations in general). However, the Final keyword in Java only protects the reference, not the state of the referenced object. Nothing prevents you from modifying the state of the object in the lambda. Consider this simple counter implemented as an object instead of a basic int.


An instance of INC can modify its state even if it is declared FINAL. We'll use it to index observable items. Note that while Java doesn't force us to explicitly declare it Final, if we try to change the reference while using it in the lambda, it will generate an error.

output:

So far it looks fine. Let's see what happens when we try to subscribe to the second observable.

output:

The second subscriber sees that the index starts at 5, which doesn't make sense. While the bug here is straightforward to find, side effects can lead to bugs that are much more subtle.

Composing data in a pipeline

The safest way to use state in Rx is to include it in the emitted data. We can pair an item with its index using a scan.

output:

The result is now valid. We removed the shared state between the two subscriptions, now they cannot affect each other.

do

In some cases we do want side effects, such as when logging. Subscription methods always have side effects, otherwise they are useless. We could put the logging in the subscriber's body, but this has two drawbacks:

1. We mixed the less interesting code for logging with the key code for subscription.

2. If we wanted to record an intermediate state in our pipeline, such as before and after mapping, we would have to introduce an additional subscription for this, which wouldn't necessarily be able to see exactly what the consumer sees and what they time to see it.

The next set of methods help us declare side effects in a cleaner way:

As we can see, they perform actions when items are emitted. They also return observable<T>, which means we can use them between operators in the pipeline. In some cases you can use a map or filter to achieve the same result. Using Doon* is better because it records your intent to have side effects. Below is an example:

output:

We reused the handy PrintSubcriber from the previous chapter. The "do" method is not affected by transformations later in the pipeline. We can record what the service produces regardless of what the consumer actually consumes. Consider the following services:

output:

We log everything produced by the service, even if the user modifies and filters the results.

At this point, the difference between the different variants of "do" should be obvious. In summary:

  • doOnEach Runs when any notification is given
  • doOnNext run when the value is emitted
  • doOnError Runs when the observable terminates with an error
  • doOnCompleted Runs when the observable terminates without error
  • doOnTerminate run when the observable terminates

A special note is onTerminate, which ends with onCompleted or onError just before the observable terminates. There is also a method finallyDo which will run immediately after the observable terminates.

doOnSubscribe, doOnUnsubscribe

Subscribing and unsubscribing are not events emitted by observables. They can still be thought of as events in a general sense, and when they happen, you might want to perform some action. Most likely, you will use them for logging.

output:

Encapsulating with AsObservable

RX is designed in a functional programming way, but it exists in an object-oriented environment. We must also guard against the dangers of object orientation. For services that return observables, consider this naive implementation.

The code above doesn't prevent naughty consumers from changing yours with their own. After this, subscriptions that completed before the change will no longer receive items because you no longer call onNext on the correct topic. Obviously we need to hide access to the target

Now, our citations are safe, but we still expose a subject's citations. Anyone can call our topic on Next and inject values ​​into our sequence. We should just return the observable<T> which is an immutable object. Subjects extend observability, we can cast our objects

Our API looks safe now, but it's not. There's nothing stopping the user from discovering that our observable is actually a Subject (using instanceof for example), convert it to a Subject, and use it as before.

asObservable

The idea behind the "observable" approach is to wrap an extension of an "observable" into an actual "observable" that can be safely shared, since "observables" are immutable.

Now we have well protected our target. This protection not only prevents malicious attacks, but also errors. We've mentioned earlier that these themes should be avoided when alternatives exist, and now we've seen examples of why. Subjects introduced states to our observed subjects. Calls to onNext, onCompleted, and onError change the order that the consumer will see. An observable built with any factory method or operator of an observable is immutable as long as we don't generate side effects ourselves, as we've seen with problems with side effects.

Mutable elements cannot be protected

As one might expect, the Rx pipeline forwards the reference to the object without creating a copy (unless we create a copy ourselves in the provided function). Modifications to objects are visible to every location in the pipeline that uses them. Consider the following mutable class:

Now, we show an observable type and two subscriptions.

output:

The first subscriber is called first by each item and its role is to modify the data. Once the first subscriber completes, the same reference is passed to the second subscriber, only now the data is changed in a way that is not declared in the producer. Developers need to have a solid understanding of Rx, Java and their environment to reason about the order of modifications and then argue that such code will run as planned. An easier way is to avoid mutable state entirely. Observables should be treated as sequential notifications of resolved events.

 

Original link:

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

If you have anything to discuss, you can add my WeChat public account:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324931965&siteId=291194637