到目前爲止,咱們已經學會了如何建立可觀測數據 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