2016/11/12

FluxJava: 給 Java 使用的 Flux 函式庫

為何選擇 Flux


設計上遇到的問題

最初在接觸 Flux 時就有一種驚豔的感覺,長久以來在設計上所出現的困擾似乎出現了曙光。在 Flux 還沒有出現之前,MVx 系列 (MVC、MVP、MVVM) 的 Design Pattern 就一直引領風潮。這類型的 Design Pattern 成功地解決了特定的問題,但卻也形成了某些尾大不掉的隱憂。在畫面不多、顯示資訊單純的應用程式中問題不容易顯現,但隨著程式複雜度的昇高,設計上所隱含的矛盾也不住地增強。

MVx 系列的設計在概念上是一個畫面對應一種資料類型,畫面專責顯示與處理該類型的資料。很直覺、也很有效地把功能區分成一組、一組的單元。水能載舟亦能覆舟,正所謂成也蕭何、敗也蕭何。就是因為每一組 MVx 太過獨立、區隔性太強,當出現整合式畫面的需求時,會造成在設計上進退兩難的抉擇。

假設程式中有一個畫面叫 Dashboard,需要整合客戶、訂單、存貨的資料。試問,這時是要設計一個新的 Model 納入所有資料?還是打破規則讓一個 View 對應多個 Model?

有人也許會問:這是問題嗎?

如果只是期望程式能夠執行,那的確算不上是個問題。但是如果要考慮到程式碼的可維護性,就必須要維持在設計上的一致性,這點在程式愈複雜的情況下愈顯重要。否則就不需要搞什麼 Design Pattern,就隨性而為、讓一切都歸於渾沌就好了。

再舉另一個例子,假設要開發的是線上購物的訂單畫面,下單時要提供客戶資料、訂單資料、刷卡資料。依據之前的原則,所有的資訊都會被設計納在一個單一的 Model 內。當某一天高層突然下指令要把購物流程改成 Wizard 的方式,每個步驟各自獨立成一個畫面。試問在這樣的情況下,開發新畫面時是讓 Model 拆解成多個?還是維持原本的樣子?

如果要維持原本的樣子,由於每一組的 MVx 都是獨立的,如何傳遞 Model?誰要負責控制傳遞的順序?又該如何保留 Model 的狀態?好吧!那就拆開...

拆開之後,問題似乎解決了,但此時高層又說了,這個程式要跨平台,所以二種類型的畫面都要有...

Flux 所提供的效果

Flux 的架構則是打破這層膠著的狀態,在其單向資料流的原則之下,View 只要管顯示資料,不管資料的來源是一個還是多個。而被通知資料有異動時,也是依循相同的方式來獲取資料,刷新畫面。至於要如何異動資料與 View 無關,只要把異動的資訊傳出去,接著就像戰機上的飛彈一樣可以射後不理。

在這樣的設計之下,以之前 Dashboard 的例子,不管是單一的畫面負責顯示所有的資料,還是畫面上分割成許多不同的元件來分別顯示特定的資料,都不會有設計上的違和感。而另一個例子同樣也適用,無關後端的資料規劃方式,View 只要專注在選墿合適的資料來源、考量如何顯示資料上即可。

Flux 只能用在有 UI 的情境之下?不儘然,並不是只有人才會輸入或需要取得回應。在有明確的邊界之狀況下,像是網路或是因設計的考量所形成邏輯上的 Layer,這種可以用來把資料供給端及接收端做有效的分離,以便進行分工、測試等等作業的架構,都可以考慮套用 Flux 的概念。

如何實作

俗話說得好,知易行難。了解 Flux 的運作過程是一回事,但要把這些過程落實到設計之中、形成程式碼又是另外一回事。Facebook 並沒有為 Java 的開發環境開發一套符合 Flux 的函式庫,而 Java 的環境相較於 JavaScript 又更加地多元化,加大了使用上的不確定性。為了避免在開發上每次都要反覆進行類似的工作,於是就依據過去的工作經驗,利用抽象化的手法及自動生成的概念,實作了一個 Framework,讓想要在 Java 的專案中使用 Flux 的人可以輕易的上手。

接下來會針對這個 Framework 做個簡單的說明。

取得 Binary

最新版本的 Jar 檔可以在 Github 的 Release 頁面中下載。

設定

如果是使用 Gradle 來建構程式,則所下載到的檔案可以送到 build.gradle 設定參照的目錄下。如果是 Android 的專案,則是放到 libs 的目錄下即可。

在專案中有使用 fluxjava-rx 時,應該也會需要在 build.gradle 中增加以下的內容:

在使用之前

在 Github 的 Repository 中,FluxJava 的函式庫程式碼放在 fluxjava 的目錄下,並且在 demo-eventbus 目錄下搭配一個示範用的 Android 專案。這個示範的專案是一個只有單一 Activity 的簡易 Todo 應用程式。在這個 App 中可以展示以下的功能:
  • 顯示 Todo 清單
  • 在不同使用者間切換 Todo 清單
  • 新增 Todo
  • 關閉/重啟 Todo

在這個示範專案中使用 greenrobot 的 EventBus 來協助 Dispatcher 和 Store 發送訊息。

如果想要與 RxJava 搭配使用,可以看一下 fluxjava-rx 目錄,裡面有 FluxJava 為 RxJava 所開發的 Addon。同時,有一個與之配對的示範專案在 demo-rx 目錄下,是由 demo-eventbus 複製過來修改的。在這個示範專案中,原本的 EventBus 以 fluxjava-rx 所提供的 RxBus 取代。而基於 RxJava 1.x 函式庫的 RxBus 所提供的功能和 EventBus 的功能相同。

如何使用


準備工作

  • Bus
    Dispatcher 和 Store 會呼叫 Bus 來傳送訊息。Bus 必須要實作 IFluxBus 的介面,實作時可以使用任何的 Bus 方案,像是:Otto、EventBus,或是自行開發的方案。如果有同時引用 fluxjava-rx,則可以直接使用 RxBus 來提供傳送訊息的功能。
  • Action
    Dispatcher 使用 Action 來通知 Store 要進行的工作。在 Action 中有二個屬性,一個是 Type、一個是 Data。Type 用來讓 Store 識別要對資料進行的動作,Data 則是該動作的附屬資訊。以示範的專案來說,當一個新的 Todo 從介面上被傳進來,則新 Todo 的內容會被放在 Data 欄位中。
  • ActionHelper
    ActionHelper 協助 ActionCreator 決定產生何種 Action,並且協助 ActionCreator 將目前傳進來的資料格式轉成可被處理的格式。
  • Store
    Store 負責截收由 Dispatcher 所送出的 Action,並根據 Action 上的資訊進行對應的資料處理。當資料處理完成,Store 會再送出一個資料異動的事件,讓事件的接收者可用以反應新的資料狀態。
  • StoreMap
    StoreMap 是一個一對一的對照表,在 Framework 中使用這一個對照表來產生需要的 Store Instance。假設 Action 和 Store 的關係是一對一的,則 Action 的型別可以用來做為 Store 型別的鍵值。像是在示範的專案中可以看到有二個 Action,分別是 UserAction 及 TodoAction,並且各自會對應到一個 Store 的型別。因此,與 TodoAction 配對的 TodoStore 就會被產生來負責處理與 Todo 相關的資料要求。

初始化程序

在 FluxJava 中,FluxContext 是用來做為整個程序開始的進入點。FluxContext 被設計成 Singleton,負責整合 Framework 中相關的元件,並且管理特定元件的 Instance。

FluxContext 的 Instance 可以由其內含的 Builder 來建立,示範的程式碼如下:

開始發送要求

在取得使用者透過 UI 元件所輸入的資料後,接下來可以利用 ActionCreator 來推送 Action,ActionCreator 的 Instance 可經由 FluxContext 來取得。Framework 預設所提供的 ActionCreator 只有一項功能 sendRequest,呼叫的程式碼要傳入 Id 及使用者輸入的資料。其中,Id 是用來決定要產生的 Action 型別。使用者輸入的資料可以在呼叫 sendRequest 後,經由 ActionHelper 轉成 Store 所需的格式。

以下為示範的程式碼:
sendRequest 有提供二種版本的實作,同步和非同步。非同步的版本會先建立一個新的 Thread 之後,在新的 Thread 中執行。如果需要特別管控 Thread 的使用或是想要使用 Thread Pool,則可以呼叫同步的版本來達到目的。

進行資料處理

要進行資料處理需在 Store 中攔截指定的 Action,攔截的方法會依據所使用的 Bus 方案而不同。以示範專案的例子來說,要在 Store 中新增一個搭配特定 Annotation 的方法。相關的程式範例如下:
如果是使用 fluxjava-rx,則 Store 可以繼承自 RxStore,此時只要覆寫 RxStore 中的 onAction 方法即可。相關的程式範例如下:

反應資料異動

跟 Store 一樣,UI 元件要依據使用的 Bus 方案來接收由 Store 所發出的資料異動事件。在 EventBus 的例子中:
在 RxBus 的例子中:










0 意見:

張貼留言