封面故事:首屆 Java Community Conference、首場 Keynote、首班車 …
在開始之前,先對 Java Community Conference 2014 的工作人員致謝,辛苦你們了 … Orz
感謝門機組長黃牙牙提供照片 … Orz
各位大家好,非常榮幸在下能擔任 Java Community Conference 首屆 Keynote 講者。
我想,大家對模式並不陌生,Java 8 則帶來了Lambda 等設計元素,那麼 Java 在模式與典範上,會有什麼樣的可能性,是你跟我都會感興趣的東西,因此促成了〈Java 8 Patterns〉這個題目的定案。
我今天要分享的內容,大致上依照這樣的流程,每個模式會使用 Java 8 中某個或某些 API 來實際說明,我會談到 Monad,不過別被嚇到了,其實你會看到,Monad 也是個模式,至少在 Java 中是這樣的!
至於 Functional Reactive 這個程式設計典範,在 Java 8 釋出之後,也成為了 Java 開發者可行的設計選項之一,你也許已經聽過 Functional Reactive,也許在未來你不避免地要去研究與使用它,然而它融合了不少抽象的概念在裏頭而不容易理解,試著將我目前對它的認識分享出來給各位,讓大家在認識它時多一份參考,是我將之放在這個議程之目的。
Gof 的 23 個設計模式是大家再熟悉不過的模式,Java 8 中可以找到不少實作,1994 年至今,23 個設計模式也不少各自的變化與應用,逐一探討並不是這個議程的全部,既然題目是〈Java 8 Patterns〉,那就來探討其中一個 API 實現的 Fluent Decorator!看看在 Lambda 導入後,Decorator 模式有了哪種實現的可能性。
一開始還是從最簡單的開始,在 Java 8 之前,想排序的方式之一就是指定 Comparator 實作,方式之一就是採匿名類別。
在 Java 8 中,可以使用 Lambda 語法,就不用寫得像匿名類別那樣囉囉嗦嗦的了。如果你稍微看過一些 Java 8 Lambda 的文章,應該也知道這種情況還可以使用方法參考,看起來更簡潔易讀,嗯!在 Java 8 出來半年多的現在,這已經不能當作演講時的梗了 … XD
眼尖的觀眾可能會發現,等一下!一開始的例子是反向排序喔!你這邊是順向排序!沒錯,被抓包了 … XD
好吧!其實在 Java 8 中可使用 Comparator 的 reverseOrder 方法,傳回一個反向排序用的 Comparator,當然,reverseOrder 的設計方式,不必在 Java 8 中也可以做到!
那就使用 comparing 方法吧!comparing(String::toString) 會傳回一個順向排序的 Comparator,這樣就有 Java 8 的感覺了,而呼叫它的 reversed 方法,會建立一個與之順序相反的 Comparator。
comparing 方法其實是高階函式的概念,高階函式是指一個函式,可以接受函式作為引數,或者可傳回函式,或者兩者都有,我知道這些泛型很嚇人 … 不過在這邊 Function 本身就是函式的概念,而 Comparator 其實也是,因為它們都只有一個方法要實作 …
來實際看看程式碼好了,在這邊你看到匿名類別的實現,你應該也知道 Java 8 中可使用 Lambda 語法來取代,Lambda 語法用來傳遞一塊程式碼很好用,也就是將程式碼當作資料傳遞時很好用 …
實際上,在 Java 8 之前,基本上只有物件的概念,沒有函式的概念,因此,當開發者想要將程式碼當作資料傳遞時,就得使用一種物件,也就是匿名類別實例,建立匿名類別實例不是我們真正的目的,將程式碼當作資料傳遞才是,也就是 Code as data!
回過頭來繼續看排序需求,如果除了想反向排序之外,還想將 null 元素排在非 null 元素之前要怎麼做呢?在 Java 8 中可以使用 nullFirst(reverseOrder) 。
其實,這不用 Java 8 也可以實現,基本上就是裝飾模式,你可以設計一個 NullsFirst 來裝飾既有的 Comparator,就可以達到這個目的,只不過,相較於直接使用 new 建立 NullsFirst 實例,用個 nullsFirst 方法來封裝,可以建立流暢的風格。
再來看個排序需求,這個需求是,嗯?你可以一眼看出這個需求嗎?好吧!取個適當的類別名稱會比較好,至少 NullsFirstStringLengthComparator 會告訴我,null 的元素排在前面,之後依字串長度來排序。
只是這個類別中某些排序模式無法重用,你有沒有辦法重構這程式,並讓它呈現流暢風格?
像是 nullsFirst(byStringLength())?還不錯,只不過,無論你是使用匿名類別或 Lambda,你都得在 byStringLength 中自行建立 Comparator,而且,每次你都是取得 String 的 length 來比較而已,這種模式很常見,你只是取得某物件的某值來比較,決定傳回 0、-1 或 1。
這種情況下,可以使用 Java 8 中的 comparing 等方法,它們可以為你建立 Comparator,只要你告訴他,根據哪個值來排序就可以了,因此就可以形成 nullsFirst(comparingInt(String::length)) 的流暢寫法。
形成巢狀方法呼叫的流暢風格,並不是 Java 8 中唯一的方式,來看另一個更複雜的例子,排序時先依 last name,接著依序比較 first name、zip code 等,現在請各位在心中默想一下,親自實作 Comparator,那會形成多複雜的流程?寫出來的程式也很難一眼看出排序意圖。
使用 Java 8 的話,可以使用 comparing(Customer::getLastName) 建立依 last name 排序的 Comparator,再使用 thenComparing(Customer::getFirstName)、thenComparing(Customer::getZipCode) 來裝飾出想要的 Comparator,而且,可以一眼看出排序意圖。
以上的流暢風格,基本上是因為 Lambda 等元素才得以實現,很簡單的道理,可以試著將之前的範例全部改用匿名類別實現,同樣都是 code as data,匿名類別實現的可讀性,一定讓你不敢恭維。
其實,就算只是實現傳統的 Decorator 模式,改用 Lambda 就可以受惠良多了,像是這邊的 reverseOrder,在這邊看到,使用了一個具名的 NullsFirst 類別來實現 nullsFirst 方法。
事實上,如果其他地方不會用到 NullsFirst 類別的話,此處也可以使用 Lambda 來實現,這樣可以少一個類別。
In, Out, In, Out, Shake It All About?這是什麼奇怪的模式?In, Out, In, Out 是個以前不容易察覺,或者察覺了,又因為匿名類別囉嗦,就乾脆把原程式碼放著不管不重構的情況,這個名稱是在《Java 8 Lambdas》這本書中提出的。
這是進行日誌時一段很常見的程式片段,expensiveOperation(context) 是一個昂貴的運算,用來建立要日誌的訊息,為了效能考量,你會使用 isLoggable 方法檢查一下日誌層級(Level),只有在符合層級的情況下,才會呼叫貴貴的 expensiveOperation(context),建立日誌訊息並記錄。
其實這段程式碼,除了框框圈起來的程式碼之外,用到的資訊與方法都是屬於 Logger 本身。
還記得《重構》這本書中,第一章的範例嗎?其中有一段說明是這樣寫的,它使用了來自 rental 的資訊,卻沒有使用任何來自 customer 的資訊,顯然地,這方法位於錯誤的物件之上。
把這段話套用到這個程式碼上,也蠻適用的 … 這段程式碼區塊使用了來自 Logger 的訊息,卻只是為了將某個值推給物件 …
這段程式碼區塊顯然放在錯的物件上了,只是,如果將這段程式碼放入 Logger 之中的話,要怎麼傳入計算訊息用的程式碼到 Logger 中?匿名類別!對!不過,那是 Java 8 前窮人將程式碼當作資料傳送的方式,如果一開始就是這麼設計的話,因為匿名類別很囉嗦,你也不一定會想用,談到將程式碼當作資料傳送,在 Java 8 中就是使用 Lambda。
因此,在 Java 8 中就將這樣的需求實現了,severe 等方法,現在可以接受 Suppiler 實例,而你可以使用 Lambda 表達式來實現它。
因此,原本計算昂貴的程式碼片段,現在就可以改使用 Lambda 表達式,傳給 severe,它不會馬上執行,只會在符合日誌層級時執行。
在 Java 8 中,除了 Logger API 將這類以前視而不見的程式異味(Bad smell)做了重構之外,另一個明顯的例子就是 Map,看看這段典型的例子,你也檢查 key 有無對應的 value,如果沒有,也就是 null 的話,再計算出 value 並置入 Map 之中。這段程式碼顯然地,使用了來自 Map 的資訊,結果只是為了將某個值置入 Map。
就像先前的 Logger 範例,這邊的例子顯然地,程式碼區塊也放錯物件了,過去不是不能重構這類程式碼,只是重構之後得使用囉嗦的匿名類別,似乎沒有比較好,只好放著不管。
在 Java 8 有了 Lambda 之後,這樣的程式碼就可以重構了,就這邊的例子,可以使用 computeIfAbsent,傳給它的 Lambda 表達式,只有在 key 沒有對應的值時才會執行,計算出來的值也會置回 Map。
不只有 computeIfAbsent,Map 上的 computeIfPresent、compute、merge 等,都是這類程式碼重構的成果,你只要將程式碼當作資料傳給它們,它們就會完成你想要做的事。
下一個要談的模式是孤伶伶地重寫,這個模式名稱也是在《Java 8 Lambdas》中提出的。
這個模式的特徵還蠻常見的,你繼承了一個類別,卻只是重新定義其中一個方法,一個例子是 ThreadLocal,此類別的實例可以為每個執行緒提供一個獨自擁有的值副本,當某個執行緒首次要取得值時,會呼叫 initialValue 方法,因此,你必須重新定義它來提供初值。
那麼,重新定義一個方法的概念是什麼呢?實際上,你的目的是重新提供一塊程式碼給該方法!談到了提供程式碼,就又想到 Lambda 了!
在 Java 8 中,ThreadLocal 提供了一個 withInitial 方法 …將你原本要放在重新定義方法中的程式碼區塊,改為 Lambda 傳給 withInitial,這會傳回一個 ThreadLocal 實例,效果與上面的匿名類別相同,然後,程式碼簡潔許多!
實際上,匿名類別的寫法,在 Java 8 之前並不是反模式,反而是撰寫這類程式碼的慣用手法。
來看看 withInitial 的程式碼,其實你可以看到,在 Java 8 之前,實際上要使用此設計也並不是問題,只不過,如果在 Java 8 之前,已經有這個 withInitial 方法,而你要使用 withInitial 方法,那 … 你還是得使用匿名類別建立 Suppiler 實例傳給它,那 … 還不如使用匿名類別直接建立一個 ThreadLocal 實例!
下一個我們要談的模式是 … 呃 … Monad … 這傢伙最近有點曝光率 … 只是每次都讓人覺得有點像 Lady Gaga 出現一樣 …
先別管 Monad 了,你有聽過 Optional 嗎?在 Java 8 中,他是號稱可用來解決噁心 null 的工具。舉例來說,這邊的程式碼是個常見的模式,你有些方法會傳回 null,為了不出現 NullPointerException,你總是得檢查 null。
也許是 Apple 的 Swift 中也有 Optional 的概念,讓 Optional 現在有點潮。假設下面這段程式碼的 order 是 Optional<Order> 型態,且確定當中包括 Order 實例的話,你可以用 get 取出當中的值,如果 Order 有個 customer 方法,傳回了 Optional<Customer>,你可以使用 isPresent 檢查有沒有值,再進一步呼叫 customer 的 get 取得 Customer。
類似地,如果 Customer 有個 address方法,傳回了 Optional<String>,你可以使用 isPresent 檢查有沒有值,再進一步決定接下來要做的事,像是傳回 address,只要以上 if 判斷中有 false 的情況,那就是傳回空的 Optional。
如果你只是這樣使用潮潮的 Optional 的話,你就會覺得有濕濕的感覺 …
濕濕的感覺是指你察覺行為上有重複了,你建立 Optional<T>、呼叫 isPresent,如果 true 就取得並將 T 對應至 Optional<U>,如果 false 就傳回空的 Optional。程式中每一層都出現這種行為重複,程式碼上你也會不斷看到 get、ifPresent、get、ifPresent …
而且,還有個程式異味,跟我們前面才看過的模式有關係,你使用了 Optional<T> 上的資訊,只是為了從中取得 Otpional<U>。
這表示,這個有行為上重複的程式碼區塊,可以放到 Optional 之中,只是,那個取得 T 並將之對應至 Optional<U> 的部份怎麼辦?將 A 對應至 B 就是 Java 8 中 Function 的職責,讓客戶端傳入 Function 實例。
至於 Function 實例的實作,因為是在 Java 8,我們可以將程式碼當成資料傳入,也就是使用 Lambda,因為判斷有無值的邏輯被封裝起來了,現在就可以重複地 flatMap 了,別管 flatMap 這名稱了,你要的不就是從 orderValue 取得 customer,然後再從 customerValue 取得 address,要不然就傳回 not available 嗎?
當然,如果這時你知道用方法參考,那程式碼就更好懂了!
這跟 Monad 有什麼關係?Monad 又有什麼重要的?Monad 當然很重要囉!不然,我就沒有主題可以在這邊胡扯了 … XD
還是別搞笑好了!其實,Monad 是一種模式,行為上的特徵就是建立 M<T>、在 M<T> 上做某些動作、取得 T 並將之對應為 M<U>,接著可能還有一些其他的動作。
剛剛談到的 Optional 就出現了 Monad 模式的行為特徵,你建立 Optional<T>、呼叫 isPresent,如果 true 就取得並將 T 對應至 Optional<U>,如果 false 就傳回空的 Optional。程式中每一層都出現這種行為重複,程式碼上你也會不斷看到 get、ifPresent、get、ifPresent …
Monad 模式就是要將重複的行為封裝起來,將那些運算情境隱藏起來,只顯露將 T 對應為 M<U> 的部份。
對 Optional Monad 來說,就是將有無值的判斷等相關邏輯隱藏起來,只顯露將 T 對應為 Optional<U> 的部份,這顯露出來的部份,就是你可以指定 Lambda,也就是指定要傳遞之程式碼的部份。
還記得這張圖嗎?希望你們都有收到 Java Community Conference 的行前通知,並且已經看過我上一場分享的〈JDK8 Functional API〉簡報。
在使用 Monad,像是 Optional 這類 Monad 結構時,你只要指定想傳遞的程式碼,因而如果你閱讀程式碼,意圖就很明顯,也就馬上就能關心到連續取得下個值的過程。
因而你就會看到類似這張圖,你從一個 Duke 建立了 Monad 來包住 Duke,然後對應至下一個被包的 Duke,再對應至下一個被包的 Duke!不同顏色的 Duke 代表不同的值。
還有什麼情況,可以採用 Monad 模式呢?來看看 for 迴圈地獄的例子,如果你想從訂單清單取得產品清單,然後從各個產品清單中取得贈品清單,最後再從贈品清單中取得有的沒的時,就很容易寫出這樣的巢狀迴圈。
你應該知道了,在 Java 8 中,使用 Stream 很方便,它有個 forEach 方法,可以結合 Lambda 語法,不過,用了之後更糟,Lambda 語法形成了巢狀 callback 地獄!不過,先別急著轉台,所謂巢狀地獄,表示其中可能有某些重複的行為模式。
在這邊,你會看到,你總是呼叫了 stream,再呼叫了 forEach,然後再呼叫了 stream,再呼叫了 forEach …
呼叫 stream 方法其實就是在建立 Stream<T>,然後,對每個 T 元素將之對應至 Stream<U> … 這就是 forEach 在做的事 …
forEach 方法其實就跟一開始的 for each 語法是同樣的概念,如果將巢狀 for 迴圈或 forEach 語法那些過程藏起來 …
就會看到如這樣的程式碼,其中 flatMap 的內部自然就是隱藏 forEach 細節的地方。結果就是,你只會看到從 order 取 lineItems 的 stream,從 lineItem 取 premiums 的 stream,從 premium 取 somethings 的 stream,最後將之收集為 List,程式意圖一眼便知。
簡單來說,flatMap 時多個 Stream 中的元素,最後會被放在一個 Stream 中,來看個更簡單的例子,如果有個 List 內含兩個 List,你要怎麼將之平坦化,成為一個 List?按照直覺的作法就是兩個 for 迴圈,如果透過 Stream 的 flatMap,就會像是這邊的程式碼。
使用動畫可以讓你更快理解這個過程,flatMap 會取得第一個 List,執行你指定的 Lambda,也就是將 List 轉為 Stream<Integer>,然後幫你展開 …
接著 flatMap 會再取得第二個 List,執行你指定的 Lambda,也就是將 List 轉為 Stream<Integer>,然後幫你展開 … 最後,就是你要的平坦化的結果。
Monad 是一種模式,行為上的特徵就是建立 M<T>、在 M<T> 上做某些動作、取得 T 並將之對應為 M<U>,接著可能還有一些其他的動作。
Monad 模式就是要將重複的行為封裝起來,將那些運算情境隱藏起來,只顯露將 T 對應為 M<U> 的部份。那麼,Java 8 中還有什麼 API 具有這種結構呢?投影片標題上其實就看到了 – CompletableFuture!
在這之前,先來看這段程式碼,它想模仿 Node.js 非同步讀取檔案的風格,使用 ExecutorService 來 submit 了一個讀取檔案的程式碼,傳回了一個 Future<String>。
因為有 Lambda,現在模仿這風格看來還不錯,你可以指定內容讀取成功之後要執行的程式碼,失敗的話要怎麼處理的程式碼。
不過,同樣地,如果事情變得複雜時,像是讀取成功之後繼續進行另一個非同步任務,就一樣會遇到巢狀 callback 地獄。
如之前說的,別急著轉台,巢狀地獄出現時,往往意謂著當中可能有某些行為模式,readFileAsync 實際上相當於建立了 Future<T>,待指定的任務完成之後,使用你指定的程式碼,將結果 T 用來呼叫 processContentAsync,相當於建立了 Future<U> …
你已經將那些執行緒處理邏輯隱藏起來了,不過並沒有善用傳回的 Future,若能善用,就能突顯 T 對應至 Future<U> 的過程,形成流暢風格。
Java 8 中使用了 CompletableFuture 來完成這件事,假設讀取檔案的任務被封裝為 readFile 方法 …
你使用 supplyAsync 指定要執行的程式碼,這會傳回一個 CompletableFuture<String>,如果你還有其他程式碼要執行,可以使用 thenComposeAsync …
這裡假設,processContentAsync 也傳回 CompletabFuture<String>,因此,你可以繼續組合想執行的程式碼,或者使用 whenComplete 來指定任務完成後要進行的動作。
thenComposeAsync 會將 T 對應至 CompletableFuture<U>,雖然名稱不同,不過,它概念上其實與 flatMap 是相同的。
CompletableFuture 其實就是一個可組合的 Future,從一個 CompletableFuture 出發,取得結果後繼續組合下個任務。如果將這張圖中任一個節點前的路徑封裝為一個方法,例如藍色與紅色封裝為一個方法。
基於該方法,你可以再組合出紫色這個任務流程 … 或可以再組合出綠色至黃色這個任務流程 … 基於同樣的概念,你可以有任意的資料流組合方式 …
既然談到了資料流組合,也許有些人會想到 Functional Reactive,這是什麼?沒聽過也沒關係,既然放了個大大的標題在這邊,表示這是接下來要探討的!
該怎麼說 Functional Reactive Programming,其實它混合了多種概念,包括 Reactive、資料流、變化的傳遞、觀察者模式、函數式風格、非同步等各式各樣的術語充斥其中,與其說是模式,不如說是集多種模式之大成的典範。
第一次看 Functional Reactive Programming,應該都會讓人想罵髒話吧!
既然要談 Functional Reactive Programming,那就先來談談什麼是 Reactive Programming,這是一種程式設計典範,以資料流的變化與傳播為設計導向。
最常被 Reactive Programming 拿來舉例的就是試算表,如果你令 B1 為 A1 儲存格加 5,就這邊而言,你會得到 15。如果令 C1 為 B1 加 10,那就這邊而言,你會得到 25。
如果你令 A1 值改變為 20 … 那麼這個改變會自動反應至 B1 … 以及 C1 …
現在假設,Java 這門程式語言直接支援 Reactive Programming 的話,那麼就這邊的程式來說,如果你指定 a 為 20,那麼結果應該自動變化為 (20, 25, 35)。
當然,實際上這在 Java 中不支援,因此是不可能有這個結果的。
不過,可以透過一些設計來達到類似效果,舉例而言,Model-View-Controller 架構中, model 的變化會自動反應在 view 的畫面上,這是物件導向風格上的一種 Reactive 實現方式。
在 MVC 中,Model 與 View 之間的關係,主要是以觀察者模式來實現。
因此,如果我們的需求是希望 a 的變化可以傳播至 b 與 c,可以透過觀察者模式來實現。在這個程式碼中,如果 a 改為 20,那麼結果就會是 (20, 25, 35)。
當然,剛剛那是很簡單的需求,在事情變得複雜之後,自行實作就會變得麻煩。舉例而言,如果你有一組樂團名單,想透過這組名單來查詢他們出過的唱片,然後從這組唱片歸納出一組 1900 年份發行的唱片,或者是從這組唱片歸納出有 solo 的樂手清單,甚至是 solo 的樂曲長度清單。
這組資料流可能是前後有一定的順序,像是唱片清單這個部份出發,可以查詢年代也可以查詢 solo,如果可以的話,你也許會想重用唱片清單前的資料流,如果這之前的資料流有變化,以其為基礎的後續資料流要能反應相關變化。
如果不想自行實作的話,可以使用 RxJava,例如它有個 rx.Observable 類別,你可以透過 from 方法,從一個 List 中建立一個 rx.Observable 實例,這個 List 可能是來自某個本地快取。
這邊的 names 方法傳回了一個 rx.Observable<String> 實例,你可以對它進行過濾,像是看看當中是否包括想搜尋的樂團名稱,如果現在想使用過濾後的名稱清單,到網路上查詢樂手清單,可以使用 flatMap,這邊假設 lookupArtist 會傳回一個 rx.Observable<Artist>,它可以是非同步來執行這個任務,對!這也是一個 Monad。
如果你對這個樂手清單有興趣,可以透過 subscribe 訂閱,像是將清單輸出至主控台,如果資料流完成並抵達,就會執行你指定的程式碼;你也可以基於這個樂手清單,繼續查詢樂手國籍。
在這邊你看到了一些有趣的方法,像是 filter、flatMap、map,感覺這個 rx.Observable 又有點像 Stream?
與其說它像 Stream,其實應該說,這是函數式風格,Functional Reactive Programming,顧名思義,就是使用函數式風格來實現 Reactive Programming,也就是融合了兩個典範。
filter、map、reduce、flatMap 等方法,其實是函數式程式設計中的常見模式,如果你之前確實有看過我前一場〈JDK8 Functional API〉的內容,應該看過這兩段程式碼,對 filter、map 等方法應該不陌生。
就因為 Functional Reactive Programming 融合了 Observable、函數式的 filter、map 等模式,加上 Reactive 本身的概念,只要其中有一項你對其感到陌生,直接觀看 Functional Reactive Programming 的文件,大概就會有置身五里霧中的感覺。
Stream 本身與 rx.Observable 確實變像的,關鍵的不同在於,Stream 主要是針對記憶體中群集處理而設計 …
rx.Observable 則是針對非同步與基於事件的系統而設計,你不用自行提取資料,如果你有訂閱,當資料流到達時,它會主動以資料來通知訂閱者,在事件系統中,資料也可以是事件,也就是成為事件流的組合與處理。
談到資料流,不知道你會不會覺得耳熟,之前談 CompletableFuture 時,就有談到資料流這個名稱,只不過 CompetableFuture 是針對一個值的資料流,一個值完成,接下來繼續下一個值的計算。
rx.Observable 則是針對一整組資料的資料流變化與傳播而設計,在這邊特意將 CompletableFuture 與 rx.Observable 的資料流示意圖放在一起,便於兩相對照。
那麼 Reactive Programming 跟非同步有什麼關係?JavaScript 創建者 Brendan Eich 曾說過,如果某個執行超過一定的秒數,使用者可能會覺得有什麼錯誤發生了,而在 Robert Miller 的研究中,這個秒數約為 100 毫秒。
可以想像地,如果可以在圖形介面上發送一個請求,然後就可以讓使用者去做別的事,反正資料流備妥時會自動通知訂閱者更新畫面,不是很好嗎?透過基本的事件處理與執行緒,其實是可以自行在程式上實作這種資料流,不過程式碼會複雜到易常難懂且不容易重用資料流。
透過 Functional Reactive Programming,你可以定義與重用資料流,因而你經常會看到 Reactive Programming 用於需要快速回應使用者的圖形介面上。
那麼,如何用 RxJava 實現非同步呢?舉剛剛看到的 lookUpArtist 方法為例,可以是這麼實作,在一個 ExecutorService 中執行非同步任務,當資料來到時,使用 onNext 方法推送給訂閱者,並以 onCompleted 標示完成。
如果你希望訂閱者在想要取消訂閱時,可以進行一些清除資源的動作,那麼可以在最後傳回一個 Subscription 實例,在它的 unsubscribe 方法中進作清除資源。
剛才將 CompletableFuture 與 rx.Observable 做了個對照,那麼,如果要小題大作一下,把 A1 變更傳播至 C1 這種概念,用 CompletableFuture 實現是可行的嗎?
這邊做了個簡單實作,a 方法是資料源,b 方法基於 a 加 5,c 方法基於 b 加 10,這麼定義之後,無論何時你定義 c 值計算完成時應進行的動作,它總是會將 a 資料的變化傳播至你指定的動作之中。
最後,來做個總結,希望你還沒有腦袋打結 … XD
其實不只一開始的 Fluent Decorator,傳統設計模式在 Java 8 中其實會有更多不同的實作樣貌與可能性,這大多是因為 Lambda 的關係。
Lamdbda 也使得過去一些不明顯的程式異味突顯出來,或是嗅得出程式異味但因為匿名類別囉嗦而不想重構的程式碼,得以有進一步的重構機會。
因此,隨著 Java 這門語言的演化,一些手法或最佳實踐也會跟著改變,就像使用匿名類別卻只是重寫單一個方法,過去是慣用手法,現在則可以有更好的實現方式。
至於過去神秘難解的 Monad,也就是一種模式,重點在能否觀察那重複的行為模式,而不是瞭解 Monad 這名詞本身。
也許你已經遇過,也許你未來才會遇到,總之,接觸 Functional Reactive Programming 對你來說,可能是無法避免的,他是多種模式、典範的合體,如果一下子無法理解,請從個別獨立的模式與典範開始。
最後,列出一些在這個簡報準備過程中,有參考過的資料。
感謝大家耐心聆聽我今天的分享 … Orz