Skip to content

Commit 2810fa6

Browse files
committed
3.7 Reworked chapter
1 parent 42a13cb commit 2810fa6

File tree

1 file changed

+22
-19
lines changed

1 file changed

+22
-19
lines changed

Part 3 - Taming the sequence/7. Custom operators.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Custom operators
22

3-
RxJava offers a very large [operator set](http://reactivex.io/RxJava/javadoc/rx/Observable.html). Counting all the overloads, the number of operators on Rx is over 300. A smaller number of those is essential, meaning that you cannot achieve an Rx implementation without them. Others are there just for convenience and for a more self-descriptive name. For example, if `source.First(user -> user.isOnline())` didn't exist, we would still be able to do `source.filter(user -> user.isOnline()).First()`.
3+
RxJava offers a very large [operator set](http://reactivex.io/RxJava/javadoc/rx/Observable.html). Counting all the overloads, the number of operators on Rx is over 300. A smaller number of those is essential, meaning that you cannot achieve an Rx implementation without them. Many are there just for convenience and a self-descriptive name. For example, if `source.First(user -> user.isOnline())` didn't exist, we would still be able to do `source.filter(user -> user.isOnline()).First()`.
44

5-
Despite many convenience operators, the operator set of RxJava is still very basic. Rx offers the building blocks that you combine, but eventually you will want to define common cases that repeat within your application. in standard Java, this would be done with custom classes and methods. In Rx, you would like to design custom operators. For example, there is no operator for calculating a running average from a sequence of numbers. But you can make one yourself:
5+
Despite many convenience operators, the operator set of RxJava is still very basic. Rx offers the building blocks that you can combine into anything, but eventually you will want to define reuseable code for repeated cases. In standard Java, this would be done with custom classes and methods. In Rx, you would like the ability to design custop operators. For example, calculating a running average from a sequence of numbers may be very common in your financial application. That doesn't already exist in `Observable`, but you can make it yourself:
66

77
```java
88
class AverageAcc {
@@ -23,7 +23,7 @@ source
2323
.map(acc -> acc.sum/(double)acc.count);
2424
```
2525

26-
That does it, but it's not reusable. In a real application, you will probably want to do the same kind of processing over many different sequences. Even if you don't, you'd still want to hide all this code behind a single phrase: "running average". Understandably, your first instinct would be to make a function out of this:
26+
That does it, but it's not reusable. In a real financial application, you will probably want to do the same kind of processing over many different sequences. Even if you don't, it would still be nice to hide all this code behind a single phrase: "running average". Understandably, a Java developer's first instinct would be to make a function out of it:
2727

2828
```java
2929
public static Observable<Double> runningAverage(Observable<Integer> source) {
@@ -61,7 +61,7 @@ runningAverage(
6161
.subscribe(System.out::println);
6262
```
6363

64-
Once again, this works, but how far can you go with this? Imagine if everything in Rx was done this way (including all the existing operators).
64+
Once again, this works, but it doesn't look 100% Rx. Imagine if everything in Rx was done like the method which we designed (including all the existing operators).
6565

6666
```java
6767
subscribe(
@@ -74,7 +74,9 @@ We're reading our pipeline in reverse! Yikes!
7474

7575
## Chaining operators
7676

77-
Rx has a particular style for applying operators, by chaining them, rather than nesting them. This style is not uncommon for objects whose methods return instances of the same type. This makes even more sense for immutable objects and can be found even in standard features, such as strings: `String s = new String("Hi").toLowerCase().replace('a', 'c');`. This style allows you to see modifications in the order that they are applied, and it also looks neater when a lot of operators are applied.
77+
Rx has a particular style for applying operators, by chaining them, rather than nesting them. This style is not uncommon for objects whose methods return instances of the same type. This makes even more sense for immutable objects and can be found even in standard Java features, such as strings:
78+
`String s = new String("Hi").toLowerCase().replace('a', 'c');`
79+
This style allows you to see modifications in the order that they are applied, and it also looks neater when a lot of operators are used.
7880

7981
Ideally, you would want your Rx operators to fit into the chain just like any other operator:
8082
```java
@@ -84,11 +86,11 @@ Observable.range(0,10)
8486
.subscribe();
8587
```
8688

87-
Many languages have ways of supporting this. Inconveniently, Java doesn't. You'd have to edit `Observable` itself to add your own methods. There's no point asking for it either, since the RxJava team are conservative about adding yet another operator. You could `extend` `Observable` and add your own operators there. In that case, as soon as you call any of the old operators, you're going to get a vanilla `Observable` back and lose access to the operators that you built.
89+
Many languages have ways of supporting this. Inconveniently, Java doesn't. You'd have to edit `Observable` itself to add your own methods. There's no point asking the RxJava team to add your idea to the operator set, since there are so many already and the RxJava team are conservative about adding yet another operator. You could `extend` `Observable` and add your own operators there. In that case, you'd no longer be able to share and combine libraries of operators.
8890

8991
### compose
9092

91-
There is a way of fitting a custom operator into the chain with the `compose` method.
93+
There is a way of fitting a custom operator into the chain, with the `compose` method.
9294

9395
```java
9496
public <R> Observable<R> compose(Observable.Transformer<? super T,? extends R> transformer)
@@ -102,7 +104,7 @@ Observable.just(3, 5, 6, 4, 4)
102104
.subscribe(System.out::println);
103105
```
104106

105-
Java doesn't let you reference a function by its name alone, so here we assumed the custom operator is in our Main class. We can see that now our operator fits perfecty into the chain, albeit with the boilerplate of calling `compose` first. For even better encapsulation, you should implement `Observable.Transformer` in a new class and move the whole thing out of sight, along with its helper class(es).
107+
Java doesn't let you reference a function by its name alone, so here we assumed the custom operator is in our Main class. We can see that now our operator fits perfecty into the chain, albeit with the boilerplate of calling `compose` first. For even better encapsulation, you should implement `Observable.Transformer` in a new class and move the whole implementation out of sight, along with its helper class(es).
106108

107109
```java
108110
public class RunningAverage implements Observable.Transformer<Integer, Double> {
@@ -169,19 +171,19 @@ public class RunningAverage implements Observable.Transformer<Integer, Double> {
169171
}
170172
```
171173

172-
We just added the parameter as a field in the operator, added constructors for the uses that we cover and used the parameter in our Rx operations. Now we can do `source.compose(new RunningAverage(5))`, where, ideally, we would be calling `source.runningAverage(5)`. Java only lets us go this far. Rx is a functional paradigm, but Java is still primarily an object oriented language and quite conservative about it.
174+
We just added the parameter as a field in the operator, added constructors for the uses that we cover and used the parameter in our Rx operations. Now we can do `source.compose(new RunningAverage(5))`, where, ideally, we would be calling `source.runningAverage(5)`. Java only lets us go this far. Rx is a functional paradigm, but Java is still primarily an object oriented language and quite conservative at that.
173175

174176
### lift
175177

176-
Every Rx operator, internally, does 3 things
178+
Internally, every Rx operator does 3 things
177179

178-
1. Subscribes to the source and observes the values.
179-
2. Transforms the observed sequence according to the operator's purpose.
180-
3. Pushes the modified sequence to its own subscribers, by calling `onNext`, `onError` and `onCompleted`.
180+
1. It subscribes to the source and observes the values.
181+
2. It transforms the observed sequence according to the operator's purpose.
182+
3. It pushes the modified sequence to its own subscribers, by calling `onNext`, `onError` and `onCompleted`.
181183

182-
The `compose` operator works with a method that makes an observable out of another. By doing this, it spares you the trouble of doing the 3 steps above manually. That presumes that you can do the transformation by using existing operators. If the operators don't already exist, or if you think you can get better performance manually, you need to receive items manually, process them and re-push them. An `Observable.Transformer` that does this would include a `subscribe` to the source `Observable` and the creation of a new `Observable` to be returned, possibly a `Subject`. As an exercise, you can try implementing an operator (e.g. reimplement `map`) without using any existing operators.
184+
The `compose` operator works with a method that makes an observable out of another. By doing this, it spares you the trouble of doing the 3 steps above manually. That presumes that you can do the transformation by using existing operators. If the operators don't already exist, or if you think you can get better performance manually, you need to receive items manually, process them and re-push them. An `Observable.Transformer` that does this would include a `subscribe` to the source `Observable` and the creation of a new `Observable` to be returned, possibly a `Subject`.
183185

184-
There's a simpler way. The `lift` operator is similar to `compose`. Instead of transforming `Observable`, it transforms `Subscriber`.
186+
There's a simpler way with `lift`. The `lift` operator is similar to `compose`. Instead of transforming `Observable`, it transforms `Subscriber`.
185187

186188
```java
187189
public final <R> Observable<R> lift(Observable.Operator<? extends R,? super T> lift)
@@ -195,6 +197,7 @@ In the next example, we will reimplement `map`, without using the existing imple
195197

196198
```java
197199
class MyMap<T,R> implements Observable.Operator<R, T> {
200+
198201
private Func1<T,R> transformer;
199202

200203
public MyMap(Func1<T,R> transformer) {
@@ -261,11 +264,11 @@ Observable.range(0, 5)
261264
When doing manual pushes to subscribers, like we do when implementing `Observable.Operator`, there are a few things to consider
262265
* A subscriber is free to unsubscribe. Don't push without checking first: `!subscriber.isUnsubscribed()`.
263266
* You are responsible for complying with the Rx contract: any number of `onNext` notifications, optionally followed by a single `onCompleted` or `onError`.
264-
* If you are going to use asynchronous operations and scheduling, use the [Schedulers](/Part%204%20-%20Concurrency/1.%20Scheduling%20and%20threading.md#Schedulers) of Rx. That will allow your operator to become [testable](/Part%204%20-%20Concurrency/2.%20Testing%20Rx.md#testscheduler).
267+
* If you need to perform asynchronous operations and scheduling, use the [Schedulers](/Part%204%20-%20Concurrency/1.%20Scheduling%20and%20threading.md#Schedulers) of Rx. That will allow your operator to become [testable](/Part%204%20-%20Concurrency/2.%20Testing%20Rx.md#testscheduler).
265268

266269
### serialize
267270

268-
If you can't guarantee that your operator will obey the Rx contract, for example because you push asynchronously, you can use the `serialize` operator. The `serialize` operator will turn an unreliable observable into a lawful, sequential observable.
271+
If you can't guarantee that your operator will obey the Rx contract, for example because you push asynchronously from multiple sources, you can use the `serialize` operator. The `serialize` operator will turn an unreliable observable into a lawful, sequential observable.
269272

270273
![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/synchronize.png)
271274

@@ -294,7 +297,7 @@ Completed
294297
Unsubscribed
295298
```
296299

297-
Despite what our observable is set to emit, the end result obeyed the Rx contract. That happened because `subscribe` will temrinate the subscription when the sequence terminates (or was supposed to). It doesn't mean that the problem will be taken care for us. There is also a method called `unsafeSubscribe`, which won't unsubscribe automatically.
300+
Despite what our observable tried to emit, the end result obeyed the Rx contract. That happened because `subscribe` will temrinate the subscription when the sequence terminates (or was supposed to have terminated). This doesn't mean that the problem will always be taken care for us. There is also a method called `unsafeSubscribe`, which won't unsubscribe automatically.
298301

299302
```java
300303
Observable<Integer> source = Observable.create(o -> {
@@ -332,7 +335,7 @@ Completed
332335
Completed
333336
```
334337

335-
Our subscriber's behaviour was identical to the previous example (we created an instance of `Subscriber` because `unsafeSubscribe` doesn't have overloads that take lambdas). We can see here we weren't unsubscribed and we kept receiving notifications.
338+
Our subscriber's intended behaviour was identical to the previous example (we created an instance of `Subscriber` because `unsafeSubscribe` doesn't have overloads that take lambdas). However, we can see here we weren't unsubscribed and we kept receiving notifications.
336339

337340
`unsafeSubscribe` is unsafe in other regards as well, such as error handling. It's usefulness is limited. The documentation says that it should only be used for custom operators that use nested subscriptions. To protect such operators from receiving and illegal sequence, we can apply the `serialize` operator
338341

0 commit comments

Comments
 (0)