FluxJava: 給 Java 使用的 Flux 庫

為何選擇 Flux

設(shè)計上遇到的問題

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

MVx 系列的設(shè)計在概念上是一個畫面對應(yīng)一種數(shù)據(jù)類型,畫面專責(zé)顯示與處理該類型的數(shù)據(jù)。很直覺、也很有效地把功能區(qū)分成一組、一組的單元。水能載舟亦能覆舟,正所謂成也蕭何、敗也蕭何。就是因為每一組 MVx 太過獨立、區(qū)隔性太強,當(dāng)出現(xiàn)整合式畫面的需求時,會造成在設(shè)計上進(jìn)退兩難的抉擇。

假設(shè)程序中有一個畫面叫 Dashboard,需要整合客戶、訂單、存貨的數(shù)據(jù)。試問,這時是要設(shè)計一個新的 Model 納入所有數(shù)據(jù)?還是打破規(guī)則讓一個 View 對應(yīng)多個 Model?

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

如果只是期望程序能夠運行,那的確算不上是個問題。但是如果要考慮到源代碼的可維護性,就必須要維持在設(shè)計上的一致性,這點在程序愈復(fù)雜的情況下愈顯重要。否則就不需要搞什么 Design Pattern,就隨性而為、讓一切都?xì)w于渾沌就好了。

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

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

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

Flux 所提供的效果

Flux 的架構(gòu)則是打破這層膠著的狀態(tài),在其單向數(shù)據(jù)流的原則之下,View 只要管顯示數(shù)據(jù),不管數(shù)據(jù)的來源是一個還是多個。而被通知數(shù)據(jù)有異動時,也是依循相同的方式來獲取數(shù)據(jù),刷新畫面。至于要如何異動數(shù)據(jù)與 View 無關(guān),只要把異動的信息傳出去,接著就像戰(zhàn)機上的飛彈一樣可以射后不理。

在這樣的設(shè)計之下,以之前 Dashboard 的例子,不管是單一的畫面負(fù)責(zé)顯示所有的數(shù)據(jù),還是畫面上分割成許多不同的組件來分別顯示特定的數(shù)據(jù),都不會有設(shè)計上的違和感。而另一個例子同樣也適用,無關(guān)后端的數(shù)據(jù)規(guī)劃方式,View 只要專注在選墿合適的數(shù)據(jù)來源、考量如何顯示數(shù)據(jù)上即可。

Flux 只能用在有 UI 的情境之下?不盡然,并不是只有人才會輸入或需要取得回應(yīng)。在有明確的邊界之狀況下,像是網(wǎng)絡(luò)或是因設(shè)計的考量所形成邏輯上的 Layer,這種可以用來把數(shù)據(jù)供給端及接收端做有效的分離,以便進(jìn)行分工、測試等等作業(yè)的架構(gòu),都可以考慮套用 Flux 的概念。

如何實現(xiàn)

俗話說得好,知易行難。了解 Flux 的運作過程是一回事,但要把這些過程落實到設(shè)計之中、形成源代碼又是另外一回事。Facebook 并沒有為 Java 的開發(fā)環(huán)境開發(fā)一套符合 Flux 的庫,而 Java 的環(huán)境相較于 JavaScript 又更加地多元化,加大了使用上的不確定性。為了避免在開發(fā)上每次都要反覆進(jìn)行類似的工作,于是就依據(jù)過去的工作經(jīng)驗,利用抽象化的手法及自動生成的概念,實現(xiàn)了一個 Framework,讓想要在 Java 的項目中使用 Flux 的人可以輕易的上手。

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

取得 Binary

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

配置

如果是使用 Gradle 來建構(gòu)程序,則所下載到的檔案可以送到 build.gradle 配置引用的文件夾下。如果是 Android 的項目,則是放到 libs 的文件夾下即可。

在項目中有使用 fluxjava-rx 時,應(yīng)該也會需要在 build.gradle 中增加以下的內(nèi)容:

dependencies {
    ...
    compile "io.reactivex:rxjava:1.2.+”
}

在使用之前

在 Github 的 Repository 中,F(xiàn)luxJava 庫源代碼放在 fluxjava 的文件夾下,并且在 demo-eventbus 文件夾下搭配一個示范用的 Android 項目。這個示范的項目是一個只有單一 Activity 的簡易 Todo 應(yīng)用程序。在這個 App 中可以展示以下的功能:

  • 顯示 Todo 清單
  • 在不同使用者間切換 Todo 清單
  • 新增 Todo
  • 關(guān)閉/重啟 Todo

在這個示范項目中使用 greenrobotEventBus 來協(xié)助 Dispatcher 和 Store 發(fā)送信息。

如果想要與 RxJava 搭配使用,可以看一下 fluxjava-rx 文件夾,里面有 FluxJava 為 RxJava 所開發(fā)的 Addon。同時,有一個與之配對的示范項目在 demo-rx 文件夾下,是由 demo-eventbus 復(fù)制過來修改的。在這個示范項目中,原本的 EventBus 以 fluxjava-rx 所提供的 RxBus 取代。而基于 RxJava 1.x 庫的 RxBus 所提供的功能和 EventBus 的功能相同。

如何使用

準(zhǔn)備工作

  • Bus
    Dispatcher 和 Store 會呼叫 Bus 來傳送信息。Bus 必須要實現(xiàn) IFluxBus 的介面,實現(xiàn)時可以使用任何的 Bus 方案,像是:Otto、EventBus,或是自行開發(fā)的方案。如果有同時引用 fluxjava-rx,則可以直接使用 RxBus 來提供傳送信息的功能。

  • Action
    Dispatcher 使用 Action 來通知 Store 要進(jìn)行的工作。在 Action 中有二個屬性,一個是 Type、一個是 Data。Type 用來讓 Store 識別要對數(shù)據(jù)進(jìn)行的動作,Data 則是該動作的附屬信息。以示范的項目來說,當(dāng)一個新的 Todo 從介面上被傳進(jìn)來,則新 Todo 的內(nèi)容會被放在 Data 欄位中。

  • ActionHelper
    ActionHelper 協(xié)助 ActionCreator 決定產(chǎn)生何種 Action,并且協(xié)助 ActionCreator 將目前傳進(jìn)來的數(shù)據(jù)格式轉(zhuǎn)成可被處理的格式。

  • Store
    Store 負(fù)責(zé)截收由 Dispatcher 所送出的 Action,并根據(jù) Action 上的信息進(jìn)行對應(yīng)的數(shù)據(jù)處理。當(dāng)數(shù)據(jù)處理完成,Store 會再送出一個數(shù)據(jù)異動的事件,讓事件的接收者可用以反應(yīng)新的數(shù)據(jù)狀態(tài)。

  • StoreMap
    StoreMap 是一個一對一的對照表,在 Framework 中使用這一個對照表來產(chǎn)生需要的 Store Instance。假設(shè) Action 和 Store 的關(guān)系是一對一的,則 Action 的型別可以用來做為 Store 型別的鍵值,或是很單純地使用一個數(shù)值來做為鍵值。像是在示范的項目中可以看到有二個常數(shù)在 Constants.java 中,分別是 DATA_USER 及 DATA_TODO,這二個常數(shù)各自會對應(yīng)到一個 Store 的型別。因此,與 TodoAction 配對的 TodoStore 就會被產(chǎn)生來負(fù)責(zé)處理與 Todo 相關(guān)的數(shù)據(jù)要求,而 User 也是套用一樣的邏輯。

初始化程序

在 FluxJava 中,F(xiàn)luxContext 是用來做為整個程序開始的進(jìn)入點。FluxContext 被設(shè)計成 Singleton,負(fù)責(zé)整合 Framework 中相關(guān)的組件,并且管理特定組件的 Instance。

FluxContext 的 Instance 可以由其內(nèi)含的 Builder 來建立,示范的源代碼如下:

FluxContext.getBuilder()
        .setBus(new Bus())
        .setActionHelper(new ActionHelper())
        .setStoreMap(storeMap)
        .build();

開始發(fā)送要求

在取得使用者透過 UI 組件所輸入的數(shù)據(jù)后,接下來可以利用 ActionCreator 來推送 Action,ActionCreator 的 Instance 可經(jīng)由 FluxContext 來取得。Framework 預(yù)設(shè)所提供的 ActionCreator 只有一項功能 sendRequest,呼叫的源代碼要傳入 Id 及使用者輸入的數(shù)據(jù)。其中,Id 是用來決定要產(chǎn)生的 Action 型別。使用者輸入的數(shù)據(jù)可以在呼叫 sendRequest 后,經(jīng)由 ActionHelper 轉(zhuǎn)成 Store 所需的格式。

以下為示范的源代碼:

Todo todo = new Todo();

FluxContext.getInstance()
        .getActionCreator()
        .sendRequestAsync(TODO_ADD, todo);

sendRequest 有提供二種版本的實現(xiàn),同步和非同步。非同步的版本會先建立一個新的 Thread 之后,在新的 Thread 中執(zhí)行。如果需要特別管控 Thread 的使用或是想要使用 Thread Pool,則可以呼叫同步的版本來達(dá)到目的。

進(jìn)行數(shù)據(jù)處理

要進(jìn)行數(shù)據(jù)處理需在 Store 中攔截指定的 Action,攔截的方法會依據(jù)所使用的 Bus 方案而不同。以示范項目的例子來說,要在 Store 中新增一個搭配特定 Annotation 的方法。相關(guān)的程序范例如下:

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onAction(final TodoAction inAction) {
    switch (inAction.getType()) {
        case TODO_LOAD:
            ...
            super.emitChange(new ListChangeEvent());
            break;
        case TODO_ADD:
            ...
            super.emitChange(new ListChangeEvent());
            break;
        case TODO_CLOSE:
            ...
            super.emitChange(new ItemChangeEvent(i));
            break;
    }
}

如果是使用 fluxjava-rx,則 Store 可以繼承自 RxStore,此時只要改寫 RxStore 中的 onAction 方法即可。相關(guān)的程序范例如下:

@Override
protected <TAction extends IFluxAction> void onAction(final TAction inAction) {
    final TodoAction action = (TodoAction)inAction;

    switch (action.getType()) {
        case TODO_LOAD:
            ...
            super.emitChange(new ListChangeEvent());
            break;
        case TODO_ADD:
            ...
            super.emitChange(new ListChangeEvent());
            break;
        case TODO_CLOSE:
            ...
            super.emitChange(new ItemChangeEvent(i));
            break;
    }
}

反應(yīng)數(shù)據(jù)異動

跟 Store 一樣,UI 組件要依據(jù)使用的 Bus 方案來接收由 Store 所發(fā)出的數(shù)據(jù)異動事件。在 EventBus 的例子中:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(final TodoStore.ListChangeEvent inEvent) {
    super.notifyDataSetChanged();
}

在 RxBus 的例子中:

todoStore.toObservable(TodoStore.ListChangeEvent.class)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                new Action1<TodoStore.ListChangeEvent>() {
                    @Override
                    public void call(final TodoStore.ListChangeEvent inEvent) {
                        TodoAdapter.super.notifyDataSetChanged();
                    }
                });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 想要追上最新的編程潮流嗎?想要導(dǎo)入最新的 Flux 編程方法嗎?這篇文章將手把手的帶你無痛進(jìn)入 Flux 的領(lǐng)域。...
    _WZ_閱讀 4,603評論 0 3
  • 想要追上最新的編程潮流嗎?想要導(dǎo)入最新的 Flux 編程方法嗎?這篇文章將手把手的帶你無痛進(jìn)入 Flux 與 Rx...
    _WZ_閱讀 926評論 0 1
  • 想要追上最新的編程潮流嗎?想要導(dǎo)入最新的 Flux 編程方法嗎?這篇文章將手把手的帶你無痛進(jìn)入 Flux 與 Rx...
    _WZ_閱讀 2,500評論 0 9
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,109評論 25 709
  • 一周內(nèi),遇到三個不同年齡段的朋友請求,全是遇到了迷茫與困惑,要我支招,收到微信的時候,起初是驚訝,隨之是感動。 沒...
    朱鳳玲閱讀 392評論 0 1

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