【演講稿】Java 8 Patterns by caterpillar | CodeData
top

【演講稿】Java 8 Patterns

分享:

【演講稿】解析 JDK8 Functional API << 前情

封面故事:首屆 Java Community Conference、首場 Keynote、首班車 …

在開始之前,先對 Java Community Conference 2014 的工作人員致謝,辛苦你們了 … Orz

JCConfStaffs

感謝門機組長黃牙牙提供照片 … Orz

各位大家好,非常榮幸在下能擔任 Java Community Conference 首屆 Keynote 講者。

我想,大家對模式並不陌生,Java 8 則帶來了Lambda 等設計元素,那麼 Java 在模式與典範上,會有什麼樣的可能性,是你跟我都會感興趣的東西,因此促成了〈Java 8 Patterns〉這個題目的定案。

java8-patterns-1

我今天要分享的內容,大致上依照這樣的流程,每個模式會使用 Java 8 中某個或某些 API 來實際說明,我會談到 Monad,不過別被嚇到了,其實你會看到,Monad 也是個模式,至少在 Java 中是這樣的!

至於 Functional Reactive 這個程式設計典範,在 Java 8 釋出之後,也成為了 Java 開發者可行的設計選項之一,你也許已經聽過 Functional Reactive,也許在未來你不避免地要去研究與使用它,然而它融合了不少抽象的概念在裏頭而不容易理解,試著將我目前對它的認識分享出來給各位,讓大家在認識它時多一份參考,是我將之放在這個議程之目的。

java8-patterns-3

Gof 的 23 個設計模式是大家再熟悉不過的模式,Java 8 中可以找到不少實作,1994 年至今,23 個設計模式也不少各自的變化與應用,逐一探討並不是這個議程的全部,既然題目是〈Java 8 Patterns〉,那就來探討其中一個 API 實現的 Fluent Decorator!看看在 Lambda 導入後,Decorator 模式有了哪種實現的可能性。

java8-patterns-4

一開始還是從最簡單的開始,在 Java 8 之前,想排序的方式之一就是指定 Comparator 實作,方式之一就是採匿名類別。

在 Java 8 中,可以使用 Lambda 語法,就不用寫得像匿名類別那樣囉囉嗦嗦的了。如果你稍微看過一些 Java 8 Lambda 的文章,應該也知道這種情況還可以使用方法參考,看起來更簡潔易讀,嗯!在 Java 8 出來半年多的現在,這已經不能當作演講時的梗了 … XD

眼尖的觀眾可能會發現,等一下!一開始的例子是反向排序喔!你這邊是順向排序!沒錯,被抓包了 … XD

java8-patterns-5

好吧!其實在 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!

java8-patterns-6

回過頭來繼續看排序需求,如果除了想反向排序之外,還想將 null 元素排在非 null 元素之前要怎麼做呢?在 Java 8 中可以使用 nullFirst(reverseOrder) 。

其實,這不用 Java 8 也可以實現,基本上就是裝飾模式,你可以設計一個 NullsFirst 來裝飾既有的 Comparator,就可以達到這個目的,只不過,相較於直接使用 new 建立 NullsFirst 實例,用個 nullsFirst 方法來封裝,可以建立流暢的風格。

java8-patterns-7

再來看個排序需求,這個需求是,嗯?你可以一眼看出這個需求嗎?好吧!取個適當的類別名稱會比較好,至少 NullsFirstStringLengthComparator 會告訴我,null 的元素排在前面,之後依字串長度來排序。

只是這個類別中某些排序模式無法重用,你有沒有辦法重構這程式,並讓它呈現流暢風格?

像是 nullsFirst(byStringLength())?還不錯,只不過,無論你是使用匿名類別或 Lambda,你都得在 byStringLength 中自行建立 Comparator,而且,每次你都是取得 String 的 length 來比較而已,這種模式很常見,你只是取得某物件的某值來比較,決定傳回 0、-1 或 1。

java8-patterns-8

這種情況下,可以使用 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,而且,可以一眼看出排序意圖。

java8-patterns-9

以上的流暢風格,基本上是因為 Lambda 等元素才得以實現,很簡單的道理,可以試著將之前的範例全部改用匿名類別實現,同樣都是 code as data,匿名類別實現的可讀性,一定讓你不敢恭維。

其實,就算只是實現傳統的 Decorator 模式,改用 Lambda 就可以受惠良多了,像是這邊的 reverseOrder,在這邊看到,使用了一個具名的 NullsFirst 類別來實現 nullsFirst 方法。

事實上,如果其他地方不會用到 NullsFirst 類別的話,此處也可以使用 Lambda 來實現,這樣可以少一個類別。

java8-patterns-10

In, Out, In, Out, Shake It All About?這是什麼奇怪的模式?In, Out, In, Out 是個以前不容易察覺,或者察覺了,又因為匿名類別囉嗦,就乾脆把原程式碼放著不管不重構的情況,這個名稱是在《Java 8 Lambdas》這本書中提出的。

java8-patterns-11

這是進行日誌時一段很常見的程式片段,expensiveOperation(context) 是一個昂貴的運算,用來建立要日誌的訊息,為了效能考量,你會使用 isLoggable 方法檢查一下日誌層級(Level),只有在符合層級的情況下,才會呼叫貴貴的 expensiveOperation(context),建立日誌訊息並記錄。

其實這段程式碼,除了框框圈起來的程式碼之外,用到的資訊與方法都是屬於 Logger 本身。

還記得《重構》這本書中,第一章的範例嗎?其中有一段說明是這樣寫的,它使用了來自 rental 的資訊,卻沒有使用任何來自 customer 的資訊,顯然地,這方法位於錯誤的物件之上。

java8-patterns-12

把這段話套用到這個程式碼上,也蠻適用的 … 這段程式碼區塊使用了來自 Logger 的訊息,卻只是為了將某個值推給物件 …

這段程式碼區塊顯然放在錯的物件上了,只是,如果將這段程式碼放入 Logger 之中的話,要怎麼傳入計算訊息用的程式碼到 Logger 中?匿名類別!對!不過,那是 Java 8 前窮人將程式碼當作資料傳送的方式,如果一開始就是這麼設計的話,因為匿名類別很囉嗦,你也不一定會想用,談到將程式碼當作資料傳送,在 Java 8 中就是使用 Lambda。

因此,在 Java 8 中就將這樣的需求實現了,severe 等方法,現在可以接受 Suppiler 實例,而你可以使用 Lambda 表達式來實現它。

因此,原本計算昂貴的程式碼片段,現在就可以改使用 Lambda 表達式,傳給 severe,它不會馬上執行,只會在符合日誌層級時執行。

java8-patterns-13

在 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 等,都是這類程式碼重構的成果,你只要將程式碼當作資料傳給它們,它們就會完成你想要做的事。

java8-patterns-14

下一個要談的模式是孤伶伶地重寫,這個模式名稱也是在《Java 8 Lambdas》中提出的。

java8-patterns-15

這個模式的特徵還蠻常見的,你繼承了一個類別,卻只是重新定義其中一個方法,一個例子是 ThreadLocal,此類別的實例可以為每個執行緒提供一個獨自擁有的值副本,當某個執行緒首次要取得值時,會呼叫 initialValue 方法,因此,你必須重新定義它來提供初值。

那麼,重新定義一個方法的概念是什麼呢?實際上,你的目的是重新提供一塊程式碼給該方法!談到了提供程式碼,就又想到 Lambda 了!

在 Java 8 中,ThreadLocal 提供了一個 withInitial 方法 …將你原本要放在重新定義方法中的程式碼區塊,改為 Lambda 傳給 withInitial,這會傳回一個 ThreadLocal 實例,效果與上面的匿名類別相同,然後,程式碼簡潔許多!

java8-patterns-16

實際上,匿名類別的寫法,在 Java 8 之前並不是反模式,反而是撰寫這類程式碼的慣用手法。

來看看 withInitial 的程式碼,其實你可以看到,在 Java 8 之前,實際上要使用此設計也並不是問題,只不過,如果在 Java 8 之前,已經有這個 withInitial 方法,而你要使用 withInitial 方法,那 … 你還是得使用匿名類別建立 Suppiler 實例傳給它,那 … 還不如使用匿名類別直接建立一個 ThreadLocal 實例!

java8-patterns-17

下一個我們要談的模式是 … 呃 … Monad … 這傢伙最近有點曝光率 … 只是每次都讓人覺得有點像 Lady Gaga 出現一樣 …

java8-patterns-18

先別管 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 的話,你就會覺得有濕濕的感覺 …

java8-patterns-19

濕濕的感覺是指你察覺行為上有重複了,你建立 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 實例。

java8-patterns-20

至於 Function 實例的實作,因為是在 Java 8,我們可以將程式碼當成資料傳入,也就是使用 Lambda,因為判斷有無值的邏輯被封裝起來了,現在就可以重複地 flatMap 了,別管 flatMap 這名稱了,你要的不就是從 orderValue 取得 customer,然後再從 customerValue 取得 address,要不然就傳回 not available 嗎?

當然,如果這時你知道用方法參考,那程式碼就更好懂了!

這跟 Monad 有什麼關係?Monad 又有什麼重要的?Monad 當然很重要囉!不然,我就沒有主題可以在這邊胡扯了 … XD

java8-patterns-21

還是別搞笑好了!其實,Monad 是一種模式,行為上的特徵就是建立 M<T>、在 M<T> 上做某些動作、取得 T 並將之對應為 M<U>,接著可能還有一些其他的動作。

剛剛談到的 Optional 就出現了 Monad 模式的行為特徵,你建立 Optional<T>、呼叫 isPresent,如果 true 就取得並將 T 對應至 Optional<U>,如果 false 就傳回空的 Optional。程式中每一層都出現這種行為重複,程式碼上你也會不斷看到 get、ifPresent、get、ifPresent …

java8-patterns-22

Monad 模式就是要將重複的行為封裝起來,將那些運算情境隱藏起來,只顯露將 T 對應為 M<U> 的部份。

對 Optional Monad 來說,就是將有無值的判斷等相關邏輯隱藏起來,只顯露將 T 對應為 Optional<U> 的部份,這顯露出來的部份,就是你可以指定 Lambda,也就是指定要傳遞之程式碼的部份。

java8-patterns-23

還記得這張圖嗎?希望你們都有收到 Java Community Conference 的行前通知,並且已經看過我上一場分享的〈JDK8 Functional API〉簡報。

在使用 Monad,像是 Optional 這類 Monad 結構時,你只要指定想傳遞的程式碼,因而如果你閱讀程式碼,意圖就很明顯,也就馬上就能關心到連續取得下個值的過程。

因而你就會看到類似這張圖,你從一個 Duke 建立了 Monad 來包住 Duke,然後對應至下一個被包的 Duke,再對應至下一個被包的 Duke!不同顏色的 Duke 代表不同的值。

java8-patterns-24

還有什麼情況,可以採用 Monad 模式呢?來看看 for 迴圈地獄的例子,如果你想從訂單清單取得產品清單,然後從各個產品清單中取得贈品清單,最後再從贈品清單中取得有的沒的時,就很容易寫出這樣的巢狀迴圈。

你應該知道了,在 Java 8 中,使用 Stream 很方便,它有個 forEach 方法,可以結合 Lambda 語法,不過,用了之後更糟,Lambda 語法形成了巢狀 callback 地獄!不過,先別急著轉台,所謂巢狀地獄,表示其中可能有某些重複的行為模式。

在這邊,你會看到,你總是呼叫了 stream,再呼叫了 forEach,然後再呼叫了 stream,再呼叫了 forEach …

java8-patterns-25

呼叫 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,就會像是這邊的程式碼。

java8-patterns-26

使用動畫可以讓你更快理解這個過程,flatMap 會取得第一個 List,執行你指定的 Lambda,也就是將 List 轉為 Stream<Integer>,然後幫你展開 …

接著 flatMap 會再取得第二個 List,執行你指定的 Lambda,也就是將 List 轉為 Stream<Integer>,然後幫你展開 … 最後,就是你要的平坦化的結果。

java8-patterns-27

Monad 是一種模式,行為上的特徵就是建立 M<T>、在 M<T> 上做某些動作、取得 T 並將之對應為 M<U>,接著可能還有一些其他的動作。

Monad 模式就是要將重複的行為封裝起來,將那些運算情境隱藏起來,只顯露將 T 對應為 M<U> 的部份。那麼,Java 8 中還有什麼 API 具有這種結構呢?投影片標題上其實就看到了 – CompletableFuture!

在這之前,先來看這段程式碼,它想模仿 Node.js 非同步讀取檔案的風格,使用 ExecutorService 來 submit 了一個讀取檔案的程式碼,傳回了一個 Future<String>。

java8-patterns-28

因為有 Lambda,現在模仿這風格看來還不錯,你可以指定內容讀取成功之後要執行的程式碼,失敗的話要怎麼處理的程式碼。

不過,同樣地,如果事情變得複雜時,像是讀取成功之後繼續進行另一個非同步任務,就一樣會遇到巢狀 callback 地獄。

如之前說的,別急著轉台,巢狀地獄出現時,往往意謂著當中可能有某些行為模式,readFileAsync 實際上相當於建立了 Future<T>,待指定的任務完成之後,使用你指定的程式碼,將結果 T 用來呼叫 processContentAsync,相當於建立了 Future<U> …

你已經將那些執行緒處理邏輯隱藏起來了,不過並沒有善用傳回的 Future,若能善用,就能突顯 T 對應至 Future<U> 的過程,形成流暢風格。

java8-patterns-29

Java 8 中使用了 CompletableFuture 來完成這件事,假設讀取檔案的任務被封裝為 readFile 方法 …

你使用 supplyAsync 指定要執行的程式碼,這會傳回一個 CompletableFuture<String>,如果你還有其他程式碼要執行,可以使用 thenComposeAsync …

這裡假設,processContentAsync 也傳回 CompletabFuture<String>,因此,你可以繼續組合想執行的程式碼,或者使用 whenComplete 來指定任務完成後要進行的動作。

thenComposeAsync 會將 T 對應至 CompletableFuture<U>,雖然名稱不同,不過,它概念上其實與 flatMap 是相同的。

java8-patterns-30

CompletableFuture 其實就是一個可組合的 Future,從一個 CompletableFuture 出發,取得結果後繼續組合下個任務。如果將這張圖中任一個節點前的路徑封裝為一個方法,例如藍色與紅色封裝為一個方法。

基於該方法,你可以再組合出紫色這個任務流程 … 或可以再組合出綠色至黃色這個任務流程 … 基於同樣的概念,你可以有任意的資料流組合方式 …

java8-patterns-31

既然談到了資料流組合,也許有些人會想到 Functional Reactive,這是什麼?沒聽過也沒關係,既然放了個大大的標題在這邊,表示這是接下來要探討的!

java8-patterns-32

該怎麼說 Functional Reactive Programming,其實它混合了多種概念,包括 Reactive、資料流、變化的傳遞、觀察者模式、函數式風格、非同步等各式各樣的術語充斥其中,與其說是模式,不如說是集多種模式之大成的典範。

第一次看 Functional Reactive Programming,應該都會讓人想罵髒話吧!

java8-patterns-33

既然要談 Functional Reactive Programming,那就先來談談什麼是 Reactive Programming,這是一種程式設計典範,以資料流的變化與傳播為設計導向。

最常被 Reactive Programming 拿來舉例的就是試算表,如果你令 B1 為 A1 儲存格加 5,就這邊而言,你會得到 15。如果令 C1 為 B1 加 10,那就這邊而言,你會得到 25。

如果你令 A1 值改變為 20 … 那麼這個改變會自動反應至 B1 … 以及 C1 …

java8-patterns-34

 

現在假設,Java 這門程式語言直接支援 Reactive Programming 的話,那麼就這邊的程式來說,如果你指定 a 為 20,那麼結果應該自動變化為 (20, 25, 35)。

當然,實際上這在 Java 中不支援,因此是不可能有這個結果的。

不過,可以透過一些設計來達到類似效果,舉例而言,Model-View-Controller 架構中, model 的變化會自動反應在 view 的畫面上,這是物件導向風格上的一種 Reactive 實現方式。

java8-patterns-35

在 MVC 中,Model 與 View 之間的關係,主要是以觀察者模式來實現。

因此,如果我們的需求是希望 a 的變化可以傳播至 b 與 c,可以透過觀察者模式來實現。在這個程式碼中,如果 a 改為 20,那麼結果就會是 (20, 25, 35)。

java8-patterns-36

當然,剛剛那是很簡單的需求,在事情變得複雜之後,自行實作就會變得麻煩。舉例而言,如果你有一組樂團名單,想透過這組名單來查詢他們出過的唱片,然後從這組唱片歸納出一組 1900 年份發行的唱片,或者是從這組唱片歸納出有 solo 的樂手清單,甚至是 solo 的樂曲長度清單。

這組資料流可能是前後有一定的順序,像是唱片清單這個部份出發,可以查詢年代也可以查詢 solo,如果可以的話,你也許會想重用唱片清單前的資料流,如果這之前的資料流有變化,以其為基礎的後續資料流要能反應相關變化。

java8-patterns-37

如果不想自行實作的話,可以使用 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?

java8-patterns-38

與其說它像 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 的文件,大概就會有置身五里霧中的感覺。

java8-patterns-39

Stream 本身與 rx.Observable 確實變像的,關鍵的不同在於,Stream 主要是針對記憶體中群集處理而設計 …

rx.Observable 則是針對非同步與基於事件的系統而設計,你不用自行提取資料,如果你有訂閱,當資料流到達時,它會主動以資料來通知訂閱者,在事件系統中,資料也可以是事件,也就是成為事件流的組合與處理。

談到資料流,不知道你會不會覺得耳熟,之前談 CompletableFuture 時,就有談到資料流這個名稱,只不過 CompetableFuture 是針對一個值的資料流,一個值完成,接下來繼續下一個值的計算。

rx.Observable 則是針對一整組資料的資料流變化與傳播而設計,在這邊特意將 CompletableFuture 與 rx.Observable 的資料流示意圖放在一起,便於兩相對照。

java8-patterns-40

那麼 Reactive Programming 跟非同步有什麼關係?JavaScript 創建者 Brendan Eich 曾說過,如果某個執行超過一定的秒數,使用者可能會覺得有什麼錯誤發生了,而在 Robert Miller 的研究中,這個秒數約為 100 毫秒。

可以想像地,如果可以在圖形介面上發送一個請求,然後就可以讓使用者去做別的事,反正資料流備妥時會自動通知訂閱者更新畫面,不是很好嗎?透過基本的事件處理與執行緒,其實是可以自行在程式上實作這種資料流,不過程式碼會複雜到易常難懂且不容易重用資料流。

透過 Functional Reactive Programming,你可以定義與重用資料流,因而你經常會看到 Reactive Programming 用於需要快速回應使用者的圖形介面上。

那麼,如何用 RxJava 實現非同步呢?舉剛剛看到的 lookUpArtist 方法為例,可以是這麼實作,在一個 ExecutorService 中執行非同步任務,當資料來到時,使用 onNext 方法推送給訂閱者,並以 onCompleted 標示完成。

如果你希望訂閱者在想要取消訂閱時,可以進行一些清除資源的動作,那麼可以在最後傳回一個 Subscription 實例,在它的 unsubscribe 方法中進作清除資源。

java8-patterns-41

剛才將 CompletableFuture 與 rx.Observable 做了個對照,那麼,如果要小題大作一下,把 A1 變更傳播至 C1 這種概念,用 CompletableFuture 實現是可行的嗎?

這邊做了個簡單實作,a 方法是資料源,b 方法基於 a 加 5,c 方法基於 b 加 10,這麼定義之後,無論何時你定義 c 值計算完成時應進行的動作,它總是會將 a 資料的變化傳播至你指定的動作之中。

java8-patterns-42

最後,來做個總結,希望你還沒有腦袋打結 … XD

其實不只一開始的 Fluent Decorator,傳統設計模式在 Java 8 中其實會有更多不同的實作樣貌與可能性,這大多是因為 Lambda 的關係。

Lamdbda 也使得過去一些不明顯的程式異味突顯出來,或是嗅得出程式異味但因為匿名類別囉嗦而不想重構的程式碼,得以有進一步的重構機會。

因此,隨著 Java 這門語言的演化,一些手法或最佳實踐也會跟著改變,就像使用匿名類別卻只是重寫單一個方法,過去是慣用手法,現在則可以有更好的實現方式。

至於過去神秘難解的 Monad,也就是一種模式,重點在能否觀察那重複的行為模式,而不是瞭解 Monad 這名詞本身。

也許你已經遇過,也許你未來才會遇到,總之,接觸 Functional Reactive Programming 對你來說,可能是無法避免的,他是多種模式、典範的合體,如果一下子無法理解,請從個別獨立的模式與典範開始。

java8-patterns-43

最後,列出一些在這個簡報準備過程中,有參考過的資料。

java8-patterns-44

感謝大家耐心聆聽我今天的分享 … Orz

分享:
按讚!加入 CodeData Facebook 粉絲群

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

熱門論壇文章

熱門技術文章