RxJava 沉思錄(二):空間維度

本文是 "RxJava 沉思錄" 系列的第二篇分享。本系列所有分享:

在上一篇分享中,我們澄清了目前有關 RxJava 的幾個最流行的誤解,它們分別是:“鏈式編程是 RxJava 的厲害之處”,“RxJava 等于異步加簡潔”,“RxJava 是用來解決 Callback Hell 的”。在上一篇的最后,我們了解了 RxJava 其實給我們最基礎的功能就是幫我們統(tǒng)一了所有異步回調(diào)的接口。但是 RxJava 并不止于此,本文我們將首先介紹 Observable 在空間維度上重新組織事件的能力。

從一個簡單的例子說起

情景:有一個相冊應用,從網(wǎng)絡獲取當前用戶的照片列表,展示在 RecyclerView 里:

public interface NetworkApi {
    @GET("/path/to/api")
    Call<List<Photo>> getAllPhotos();
}

上面是使用 Retrofit 定義的從網(wǎng)絡獲取照片的 API 的接口。大家都知道,如果我們使用 Retrofit 的 RxJavaCallAdapter 就可以把接口中的返回類型從 Call<List<Photo>> 轉(zhuǎn)為 Observable<List<Photo>>:

public interface NetworkApi {
    @GET("/path/to/api")
    Observable<List<Photo>> getAllPhotos();
}

那么我們使用這個接口展示照片的代碼應該長下面這樣:

NetworkApi networkApi = ...
networkApi.getAllPhotos()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(photos -> {
        adapter.setData(photos);
        adapter.notifyDataSetChanged();
    });

現(xiàn)在新加一個需求,請求當前用戶照片列表這個網(wǎng)絡請求,需要加入緩存功能(緩存的是網(wǎng)絡響應中的圖片的URL,圖片的 Bitmap 緩存交給專門的圖片加載框架,例如 Glide),也就是說,當用戶希望展示圖片列表時,先去緩存讀取用戶的照片列表進行加載(如果緩存里有這個接口的上次訪問的數(shù)據(jù)),同時發(fā)起網(wǎng)絡請求,待網(wǎng)絡請求返回之后,更新緩存,同時使用使用最新的返回數(shù)據(jù)刷新照片列表。如果我們選擇使用 JakeWhartonDiskLruCache 作為我們的緩存介質(zhì),那么上面的代碼將變?yōu)椋?/p>

DiskLruCache cache = ... 
DiskLruCache.Snapshot snapshot = cache.get("getAllPhotos");
if (snapshot != null) {
    // 讀取緩存數(shù)據(jù)并反序列化
    List<Photo> cachedPhotos = new Gson().fromJson(
        snapshot.getString(VALUE_INDEX),
        new TypeToken<List<Photo>>(){}.getType()
    );
    // 刷新照片列表
    adapter.setData(photos);
    adapter.notifyDataSetChanged();
}
NetworkApi networkApi = ...
networkApi.getAllPhotos()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(photos -> {
        adapter.setData(photos);
        adapter.notifyDataSetChanged();

        // 更新緩存
        DiskLruCache.Editor editor = cache.edit("getAllPhotos");
        editor.set(VALUE_INDEX, new Gson().toJson(photos)).commit();
    });

上面的代碼就是最直觀的可以解決需求的代碼,我們進一步思考一下,讀取文件緩存也屬于耗時操作,我們最好把它封裝為異步任務,既然網(wǎng)絡請求已經(jīng)被封裝成 Observable 了,我們嘗試把讀取文件緩存也封裝為 Observable :

Observable<List<Photo>> cachedObservable = Observable.create(emitter -> {
    DiskLruCache.Snapshot snapshot = cache.get("getAllPhotos");
    if (snapshot != null) {
        List<Photo> cachedPhotos = new Gson().fromJson(
            snapshot.getString(VALUE_INDEX),
            new TypeToken<List<Photo>>(){}.getType()
        );
        emitter.onNext(cachedPhotos);
    } 
    emitter.onComplete();
});

到目前為止,發(fā)起網(wǎng)絡請求和讀取緩存這兩個異步操作都被我們封裝成了 Observable 的形式,前面做了這么多鋪墊,接下來進入正題:把原先的面向 Callback 的異步操作統(tǒng)一改寫為 Observable 的形式以后,首先帶來的好處就是可以對 Observable 在空間維度上進行重新組織。

networkApi.getAllPhotos()
    .doOnNext(photos -> 
        // 更新緩存
        cache.edit("getAllPhotos")
            .set(VALUE_INDEX, new Gson().toJson(photos))
            .commit()
    )
    // 讀取現(xiàn)有緩存
    .startWith(cachedObservable)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(photos -> {
        adapter.setData(photos);
        adapter.notifyDataSetChanged();
    });

調(diào)用 startWith 操作符后,會生成一個新的 Observable,新的 Observable 會首先發(fā)射傳入的 Observable 包含的元素,而后才會發(fā)射原來的 Observable 包含的元素。例如 Observable A 包含 a1, a2 兩個元素, Observable B 包含 b1, b2 兩個元素,那么 b.startWith(a) 返回的新 Observable 發(fā)射序列順序為: a1, a2, b1, b2?!?參考資料:StartWith

在上面的例子中,我們連接了網(wǎng)絡請求和讀取緩存這兩個 Observable,原先需要分別處理結果的兩個異步任務,我們現(xiàn)在把它們結合成了一個,指定了一個觀察者就滿足了需求。這個觀察者會被回調(diào) 2 次,第一次是來自緩存的結果,第二次是來自網(wǎng)絡的結果,體現(xiàn)在界面上就是列表刷新了兩次。

這里引發(fā)了我們的思考,原先 Callback 的寫法,如果我們有 n 個異步任務,我們就需要指定 n 個回調(diào);而如果在 n 個異步任務都已經(jīng)被封裝成 Observable 的情況下,我們就可以對 Observable 進行分類、組合、變換,經(jīng)過這樣的處理以后,我們的觀察者的數(shù)量就會減少,而且職責會變的簡單而直接,只需要對它所關心的數(shù)據(jù)類型做出響應,而不需要關心數(shù)據(jù)從何而來,經(jīng)歷過怎樣的變化。

我們再進一步,上面的例子再加一個需求:如果從網(wǎng)絡請求回來的數(shù)據(jù)和緩存中提前響應的數(shù)據(jù)一致,就不需要再刷新一次了。也就是說,如果緩存數(shù)據(jù)和網(wǎng)絡數(shù)據(jù)一致,那緩存數(shù)據(jù)刷新一次列表以后,網(wǎng)絡數(shù)據(jù)不需要再去刷新一次列表了。

我們考慮一下,如果我們使用傳統(tǒng) Callback 的形式,指定了兩個 Callback 去處理這個需求,為了保證第二次網(wǎng)絡請求回來的相同數(shù)據(jù)不刷新,我們勢必需要在兩個 Callback 之外,定義一個變量來保存緩存數(shù)據(jù),然后在網(wǎng)絡請求的回調(diào)內(nèi),比較兩個值,來決定是否需要刷新界面。

但如果我們用 RxJava 如何來實現(xiàn)這個需求,該如何寫呢:

networkApi.getAllPhotos()
    .doOnNext(photos -> 
        cache.edit("getAllPhotos")
            .set(VALUE_INDEX, new Gson().toJson(photos))
            .commit()
    )
    .startWith(cachedObservable)
    // 保證不會出現(xiàn)相同數(shù)據(jù)
    .distinctUntilChanged()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(photos -> {
        adapter.setData(photos);
        adapter.notifyDataSetChanged();
    });

distinctUntilChanged 操作符用來確保 Observable 發(fā)射的元素里,相鄰的兩個元素必須是不相等的。 參考資料:Distinct

與原先的寫法相比,只多了一行 .distinctUntilChanged() ( 我們假設用于比較兩個對象是否相等的 equals 方法已經(jīng)實現(xiàn) ),就可以滿足,在網(wǎng)絡數(shù)據(jù)和緩存數(shù)據(jù)一致的情況下,觀察者只回調(diào)一次。

我們比較一下使用 Callback 的寫法和使用 Observable 進行組裝的寫法,可以發(fā)現(xiàn),使用 Callback 的寫法,經(jīng)常會由于需求的變化,導致 Callback 內(nèi)部的邏輯發(fā)生變動,而使用 Observable 的寫法,觀察者的核心邏輯則較為穩(wěn)定,很少發(fā)生變化(本例中為刷新列表)。Observable 通過內(nèi)置的操作符對自身發(fā)射的元素在空間維度上重新組織,或者與其他的 Observable 一起在空間維度上進行重新組織,使得觀察者的邏輯簡單而直接,不需要關心數(shù)據(jù)從何而來,從而使觀察者的邏輯較為穩(wěn)定。

一個復雜的例子

情景:實現(xiàn)一個具有多種類型的 RecyclerView,如圖所示:

image

假設列表中有 3 種類型的數(shù)據(jù),這 3 種類型共同填充了一個 RecyclerView,簡單起見,我們定義 Retrofit 接口如下:

public interface NetworkApi {
    @GET("/path/to/api")
    Observable<List<ItemA>> getItemListOfTypeA();
    @GET("/path/to/api")
    Observable<List<ItemB>> getItemListOfTypeB();
    @GET("/path/to/api")
    Observable<List<ItemC>> getItemListOfTypeC();
}

到目前為止,情況還是簡單的, 我只要維護 3 個 RecyclerView 并分別各自更新即可。但是我們現(xiàn)在接到新加需求,這 3 種類型的數(shù)據(jù)在列表中出現(xiàn)的順序是可配置的,而且 3 種類型數(shù)據(jù)不一定全部需要展示,也就是說可能展示 3 種,也可能只展示其中 2 種。我們定義與之對應的接口:

public interface NetworkApi {
    @GET("/path/to/api")
    Observable<List<ItemA>> getItemListOfTypeA();
    @GET("/path/to/api")
    Observable<List<ItemB>> getItemListOfTypeB();
    @GET("/path/to/api")
    Observable<List<ItemC>> getItemListOfTypeC();
    // 需要展示的數(shù)據(jù)順序
    @GET("/path/to/api")
    Observable<List<String>> getColumns();
}

新加的 getColumns 接口,返回的數(shù)據(jù)形如:

  • ["a", "b", "c"]
  • ["b", "a"]
  • ["b", "c"]

首先考慮使用普通的 Callback 形式如何來實現(xiàn)這個需求。由于 3 種數(shù)據(jù)現(xiàn)在順序可變,數(shù)量也無法確定,如果還是考慮由多個 RecyclerView 來維護的話需要在布局中調(diào)用 addView, removeView
來添加移除 RecyclerView,這樣的話性能上不夠好,我們考慮把所有數(shù)據(jù)填充到一個 RecyclerView 中,不同類型的數(shù)據(jù)通過不同 ItemType 進行區(qū)分。下面的代碼中我依然使用了 Observable ,只是我僅僅把它當成普通的 Callback 功能使用:

private NetworkApi networkApi = ...
// 不同類型數(shù)據(jù)出現(xiàn)的順序
private List<String> resultTypes;
// 這些類型對應的數(shù)據(jù)的集合
private LinkedList<List<? extends Item>> responseList;

public void refresh() {
    networkApi.getColumns().subscribe(columns -> {
        // 保存配置的欄目順序
        resultTypes = columns;
        responseList = new LinkedList<>(Collections.nCopies(columns.size(), new ArrayList<>()));
        for (String type : columns) {
            switch (type) {
                case "a":
                    networkApi.getItemListOfTypeA().subscribe(data -> onOk("a", data));
                    break;
                case "b":
                    networkApi.getItemListOfTypeB().subscribe(data -> onOk("b", data));
                    break;
                case "c":
                    networkApi.getItemListOfTypeC().subscribe(data -> onOk("c", data));
                    break;
            }
        }
    });
}

private void onOk(String type, List<? extends Item> response) {
    // 按配置的順序,更新對應位置上的數(shù)據(jù)
    responseList.set(resultTypes.indexOf(type), response);
    // 把當前已返回的數(shù)據(jù)填充到一個 List 中
    List<Item> data = new ArrayList<>();
    for (List<? extends Item> itemList: responseList) {
        data.addAll(itemList);
    }
    // 更新列表
    adapter.setData(data);
    adapter.notifyDataSetChanged();
}

上面的代碼,為了避免 Callback Hell 出現(xiàn),我已經(jīng)提前把 onOk 提到了外部層次,使代碼便于從上往下閱讀。但是不知道你有沒有和我相同的感覺,就是類似這樣的代碼總給人一種不是很 “內(nèi)聚” 的感覺,就是為了把 Callback 展平,導致一些中間變量被暴露到了外層空間。

帶著這個問題,我們先分析一下數(shù)據(jù)流動:

  1. refresh 方法發(fā)起第一次請求,得到需要被展示的 n 種數(shù)據(jù)的類型以及順序。
  2. 根據(jù)第一次請求的結果,發(fā)起 n 次請求,分別得到每種數(shù)據(jù)的結果。
  3. onOk 方法作為觀察者, 會被回調(diào) n 次,按照第一個接口里返回的順序正確的匯總 2 中每個數(shù)據(jù)接口返回的結果,并且通知界面更新。

有點像寫作文一樣,這是一種 總——分——總 的結構。

Observable 在空間維度重新組織事件

接下來我們使用 RxJava 來實現(xiàn)這個需求,我們會用到 RxJava 的一些操作符,來對 Observable 進行重新組織:

NetworkApi networkApi = ...

networkApi.getColumns()
    .map(types -> {
        List<Observable<? extends List<? extends Item>>> requestObservableList = new ArrayList<>();
        for (String type : types) {
            switch (type) {
                case "a":
                    requestObservableList.add(
                        networkApi.getItemListOfTypeA().startWith(new ArrayList<ItemA>())
                    );
                    break;
                case "b":
                    requestObservableList.add(
                        networkApi.getItemListOfTypeB().startWith(new ArrayList<ItemB>())
                    );
                    break;
                case "c":
                    requestObservableList.add(
                        networkApi.getItemListOfTypeC().startWith(new ArrayList<ItemC>())
                    );
                    break;
            }
        }
        return requestObservableList;
    })
    .flatMap(requestObservables -> Observable.combineLatest(requestObservables, objects -> {
        List<Item> items = new ArrayList<>();
        for (Object response : objects) {
            items.addAll((List<? extends Item>) response);
        }
        return items;
    }))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(data -> {
        adapter.setData(data);
        adapter.notifyDataSetChanged();
    });

我們一步一步分析 RxJava 處理的具體步驟。首先是第一步,獲取需要展示的欄目列表,這是最簡單的,networkApi.getColumns() 這個方法返回是一個只發(fā)射一個元素的 Observable,這個元素即為展示的欄目列表,為了方便后續(xù)討論,假設欄目的順序為 ["a", "b", "c"], 如下圖所示:

image

接下來的操作符是 map 操作符,原來的 Observable 進行了變換,變成了一個新的 Observable,新的 Observable 還是只發(fā)射一個元素,這個元素的類型還是 List ,只不過 List 內(nèi)部的數(shù)據(jù)類型從原先的字符串(代表數(shù)據(jù)類型)變成了 Observable。Observable 發(fā)射的元素還可以是 “Observable 的 List ” 嗎?是的,沒有什么不可以 : )

image

map 操作符負責把一個 Observable 里發(fā)射的元素全部進行轉(zhuǎn)換,生成一個發(fā)射新的元素的 Observable,元素的種類會發(fā)生改變,但是發(fā)射的元素的數(shù)量不會發(fā)生改變。 參考資料:Map

這個操作,在業(yè)務上的含義是,根據(jù)上一步取回的欄目列表,即 ["a", "b", "c"],根據(jù)不同的數(shù)據(jù)類型,分別發(fā)起請求去獲取對應欄目的數(shù)據(jù)列表,例如欄目類型是 a 的話,就對應發(fā)起 networkApi.getItemListOfTypeA() 請求。這里有一點值得注意,就是每一個具體的請求后面都跟了一個 .startWith(new ArrayList<>()),也就是說每個具體請求欄目內(nèi)容的 Observable 在返回真正的數(shù)據(jù) List 之前都會返回一個空的 List ,這里這么處理的原因我們會在下一步中解釋。

接下來這一步可能是最難理解的一步了,map 操作之后,緊接著是 flatMap 操作符,而 flatMap 操作符傳入的 lambda 表達式內(nèi)部,又調(diào)用了 Observable.combineLatest 操作符,我們先從里面的 combineLatest 操作符開始講起,請看下圖:

image

combineLatest 操作符的第一個參數(shù) requestObservables,它的類型是 Observable 的 List,它就是上一步中 map 操作符進行變換之后,新的 Observable 發(fā)射的數(shù)據(jù),即由

  • networkApi.getItemListOfTypeA().startWith(...)
  • networkApi.getItemListOfTypeB().startWith(...)
  • networkApi.getItemListOfTypeC().startWith(...)

3 個 Observable 組成的 List。

combineLatest 操作符的第二個參數(shù)是個 lambda 表達式,這個 lambda 表達式的參數(shù)類型是 Object[],這個數(shù)組的長度等于 requestObservables 的長度,Object[] 數(shù)組中每個元素即為 requestObservables 中每個 Observable 發(fā)射的元素,即:

  • Object[0] 對應 requestObservables[0] 發(fā)射的元素
  • Object[1] 對應 requestObservables[1] 發(fā)射的元素
  • Object[2] 對應 requestObservables[2] 發(fā)射的元素

那這個 lambda 表達式被調(diào)用的時機是什么時候呢?當 requestObservables 中任意一個 Observable 發(fā)射一個元素時,這個元素便會和 requestObservables 中剩余的所有 Observable 最近一次 發(fā)射的元素一起,作為參數(shù)調(diào)用這個 lambda 表達式。

那么整個 combineLatest 操作符的作用就是,返回一個新的 Observable, 根據(jù)第一個參數(shù)里輸入的一組 Obsevable,按照上面說的時機,調(diào)用第二個參數(shù)里的那個 lambda 表達式,把這個 lambda 表達式的返回值,作為新的 Observable 發(fā)射的值,lambda 被調(diào)用幾次,就發(fā)射幾個元素。

參考資料:CombineLatest

我們這里 lambda 表達式內(nèi)部的邏輯比較簡單,就是把 3 個接口里返回的數(shù)據(jù)進行匯總,組成一個新的 List 。我們再回過頭看上面那張圖,我們可以看到,Observable.combinLatest 返回的新的 Observable 一共發(fā)射了 4 個元素,它們分別是:

  • []
  • [{ItemB}, {ItemB}, ...]
  • [{ItemA}, {ItemA}, ..., {ItemB}, {ItemB}, ...]
  • [{ItemA}, {ItemA}, ..., {ItemB}, {ItemB}, ..., {ItemC}, {ItemC}, ...]

前面留了一個問題沒有解釋,為什么 3 個獲取具體的欄目數(shù)據(jù)的接口需要調(diào)用 startWith 操作符發(fā)射一個空白列表,就像這樣:networkApi.getItemListOfTypeA().startWith(...),現(xiàn)在這個答案應該清晰了,如果不調(diào)用這個操作符,那么 combineLatest 操作符生成的新 Observable 將會只發(fā)射一個元素, 即上面 4 個元素的最后一個,從用戶的感受來看,必須要等所有欄目全部請求成功以后才會一次性展示,而不是漸進地展示。

說完了內(nèi)部的 combineLatest 操作符,現(xiàn)在該說外層的 flatMap 操作符了,flatMap 操作符也會生成一個新的 Observable,它會通過傳入的 lambda 表達式,把舊的 Observable 里發(fā)射的每一個元素都映射成一個 Observable,然后把這些 Observable 發(fā)射的所有元素作為新的 Observable 發(fā)射的元素。

參考資料:FlatMap

由于我們這里的情況,調(diào)用 flatMap 之前的 Observable 只發(fā)射了一個元素,所以 flatMap 之后生成的新 Observable 發(fā)射的元素,就是 flatMap 操作符傳入的那個 lambda 表達式執(zhí)行完生成的那個 Observable 所發(fā)射的元素,也就是說 flatMap 操作符執(zhí)行完后的那個新的 Observable 發(fā)射的元素,和我們剛剛討論的 combineLatest 操作符執(zhí)行完后的 Observable 發(fā)射的元素是一致的。

到這里為止,RxJava 實現(xiàn)的版本的每一步我們都解釋完了,我們回過頭重新梳理一下 RxJava 對 Observable 進行變換的過程,如下圖:

image

通過 RxJava 的操作符,我們把 networkApi 里的 4 個接口返回的 4 個 Observable,在空間維度進行了重新組織,最終把它們轉(zhuǎn)成了一個 Observable,這個 Observable 發(fā)射的元素類型是 List<Item>,而這正是我們的觀察者 -- Adapter 所關心的數(shù)據(jù)類型,觀察者只需要監(jiān)聽這個 Observable ,并更新數(shù)據(jù)即可。

我們在講 RxJava 實現(xiàn)的這個版本之前的時候,說到過 Callback 實現(xiàn)的版本不夠 內(nèi)聚,比較一下現(xiàn)在這個 RxJava 的版本,確實可以發(fā)現(xiàn)的確 RxJava 這個版本更內(nèi)聚。但是并非 Callback 版本沒有辦法做到更內(nèi)聚,我們可以把 Callback 版本里的 onOk, refresh,resultTypes, responseList 這幾個方法和字段封裝到一個對象中,對外只暴露 refresh 方法和一個設置觀察者的方法,也可以做到一樣的內(nèi)聚,但是這就需要額外的工作量了??扇绻覀兪褂?RxJava 就不一樣了,它提供了一堆現(xiàn)成的操作符,通過 Observable 之間的變換與重組,直接就可以寫出內(nèi)聚的代碼。

在上面代碼里出現(xiàn)的所有操作符中,最核心的一個操作符就是 combineLatest 操作符,仔細比較 RxJava 版本和 Callback 版本就可以發(fā)現(xiàn),combineLatest 操作符的功能其實和 Callback 版本里的 onOk 方法前半部分, resultTypes, responseList 合在一起功能是相當?shù)?,一方面負責收集多個接口返回的數(shù)據(jù),另一方面保證收集回來的數(shù)據(jù)的順序是和上一個接口返回的應該展示的數(shù)據(jù)的順序是一致的。

一種更加函數(shù)式的寫法

從代碼量上來看,RxJava 版本與 Callback 版本相差無幾,對函數(shù)式編程比較擅長的人來說,RxJava 版本里 for 循環(huán)的寫法,不夠 “函數(shù)式”,我們可以把原來的寫法改成一種更緊湊、更函數(shù)式的寫法:

NetworkApi networkApi = ...

netWorkApi.getColumns()
    .flatMap(types -> Observable.fromIterable(types)
        .map(type -> {
            switch (type) {
                case "a": return netWorkApi.getItemListOfTypeA().startWith(new ArrayList<ItemA>());
                case "b": return netWorkApi.getItemListOfTypeB().startWith(new ArrayList<ItemB>());
                case "c": return netWorkApi.getItemListOfTypeC().startWith(new ArrayList<ItemC>());
                default: throw new IllegalArgumentException();
            }
        })
        .<List<Observable<? extends List<? extends Item>>>>collectInto(new ArrayList<>(), List::add)
        .toObservable()
    )
    .flatMap(requestObservables -> Observable.combineLates(requestObservables, objects -> objects))
    .flatMap(objects -> Observable.fromArray(objects)
        .<List<Item>>collectInto(new ArrayList<>(), (items, o) -> items.addAll((List<Item>) o))
        .toObservable()
    )
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(data -> {
        adapter.setData(data);
        adapter.notifyDataSetChanged();
    });

這里引入了一個新的操作符 collectInto,用于把一個 Observable 里面發(fā)射的元素,收集到一個可變的容器內(nèi)部,本例中用它來替換 for 循環(huán)相關邏輯,具體內(nèi)容這里不再詳細展開。
參考資料:CollectInto

小結

第二個例子花了這么大篇幅來講,超出了我一開始的預期,這也可以看出來的確 RxJava 學習的曲線是陡峭的,不過我認為這個例子很好的表達我這一小節(jié)要闡述的觀點,即 Observable 在空間維度上對事件的重新組織,讓我們的事件驅(qū)動型編程更具想象力 ,因為原先的編程中,我們面對多少個異步任務,就會寫多少個回調(diào),如果任務之間有依賴關系,我們的做法就是修改觀察者(回調(diào)函數(shù))邏輯以及新增數(shù)據(jù)結構保證依賴關系,RxJava 給我們帶來的新思路是,Observable 的事件在到達觀察者之前,可以先通過操作符進行一系列變換(當然變換的規(guī)則還是和具體業(yè)務邏輯有關的),對觀察者屏蔽數(shù)據(jù)產(chǎn)生的復雜性,只提供給觀察者簡單的數(shù)據(jù)接口。

那么是否在這個例子中,是否 RxJava 的版本更好呢,我個人的觀點是雖然 RxJava 版本展現(xiàn)了其更有想象力的編程方式,但是就這個具體的例子,兩者并沒有太大的差距。RxJava 可以寫出更短更內(nèi)聚的代碼,但是編寫和理解的難度較大;Callback 版本雖然樸實無華,但是便于編寫以及理解,可維護性更好。對于兩者的好壞,我們也不要過于著急下結論,不妨繼續(xù)看看 RxJava 還有什么其他的優(yōu)勢。

(未完待續(xù))

本文屬于 "RxJava 沉思錄" 系列,歡迎閱讀本系列的其他分享:

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 本篇文章介主要紹RxJava中操作符是以函數(shù)作為基本單位,與響應式編程作為結合使用的,對什么是操作、操作符都有哪些...
    嘎啦果安卓獸閱讀 2,994評論 0 10
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    BrotherChen閱讀 1,783評論 0 10
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    無求_95dd閱讀 3,513評論 0 21
  • 引入依賴: implementation 'io.reactivex.rxjava2:rxandroid:2.0....
    為夢想戰(zhàn)斗閱讀 1,432評論 0 0
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    測天測地測空氣閱讀 685評論 0 1

友情鏈接更多精彩內(nèi)容