[譯]使用MVI打造響應式APP(四):獨立性UI組件

原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS
做者:Hannes Dorfmann
譯者:卻把清梅嗅java

這篇博客中,咱們將針對如何 如何構建獨立組件 進行探討,我將闡述爲何在我看來 父子關係會致使壞味道的代碼,以及爲什麼這種關係是沒有意義的。android

有這樣一個問題時不時涌如今個人腦海中—— MVIMVPMVVM這些架構設計模式中,多個Presenter(或者ViewModel)彼此之間是如何進行通信的?更直白點說吧,Child-Presenter是如何與Parent-Presenter通信的?git

對我來講,這種 父子關係 會產生壞味道的代碼,由於這直接 致使了父子層級之間的耦合,使得代碼難以閱讀和維護github

這種狀況下,需求的更改會影響不少的組件(對於大型系統來講,這種狀況下實現需求的變更簡直難如登天);並不是僅此而已,同時,這也 引入了難以預測的共享的狀態,其致使的問題甚至難以重現和調試。設計模式

其實這也沒那麼不堪,但我實在不理解爲什麼信息必須從Presenter A流向Presenter B呢?或者Presenter如何與另外一個Presenter進行通訊?架構

根本不必! 什麼狀況下Presenter纔會須要和Presenter進行直接的通信,是什麼事件發生了嗎?Presenter根本不須要和其它的Presenter直接通信,它們都觀察了同一個Model(或者說是業務邏輯的相同部分),這就是它們如何得到變化的通知:經過底層。app

當一些事件發生時(好比用戶點擊了View1按鈕),Presenter將信息下沉到業務邏輯。由於其它的Presenter觀察了相同的業務邏輯,所以它們從業務邏輯中接收到了一樣變化的通知(Model被更新了)。ide

關於這一點,咱們已經在 第一章節 討論了 單向數據流 的原理的重要性。函數

讓咱們經過一個真實的案例實現它:在咱們的購物App中,咱們可以將商品加入購物車,此外,有這樣一個頁面,咱們能夠看到購物車商品的內容,而且可以一次選擇或者刪除多個商品條目:學習

咱們若是可以將這樣一個複雜的界面分割成更多 精巧、獨立且可複用的UI組件 的話就太棒了。以Toolbar爲例,它展現了被選中條目的數量,以及RecyclerView展現了購物車裏條目的列表。

<LinearLayout>
  <com.hannesdorfmann.SelectedCountToolbar android:id="@+id/selectedCountToolbar" android:layout_width="match_parent" android:layout_height="wrap_content" />

  <com.hannesdorfmann.ShoppingBasketRecyclerView android:id="@+id/shoppingBasketRecyclerView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" />
</LinearLayout>
複製代碼

可是這些組件之間如何保持相互的通信呢?很明顯每一個組件都有它本身的Presenter:SelectedCountPresenterShoppingBasketPresenter。這屬於父子關係嗎?不,它們僅僅是觀察了同一個Model,該Model根據在的邏輯代碼中進行更新:

public class SelectedCountPresenter extends MviBasePresenter<SelectedCountView, Integer> {

  private ShoppingCart shoppingCart;

  public SelectedCountPresenter(ShoppingCart shoppingCart) {
    this.shoppingCart = shoppingCart;
  }

  @Override protected void bindIntents() {
    subscribeViewState(shoppingCart.getSelectedItemsObservable(), SelectedCountView::render);
  }
}


class SelectedCountToolbar extends Toolbar implements SelectedCountView {

  ...

  @Override public void render(int selectedCount) {
   if (selectedCount == 0) {
     setVisibility(View.VISIBLE);
   } else {
       setVisibility(View.INVISIBLE);
   }
 }
}
複製代碼

ShoppingBasketRecyclerView 的代碼和上述代碼的實現很是相似,所以本文不對其進行展現。然而,若是咱們認真去觀察這段代碼,你會發現SelectedCountPresenterShoppingCart有必定的耦合。

咱們徹底有可能會在其它的頁面去複用這個UI組件,所以咱們須要移除這個依賴的關係以達到複用該組件的目的。重構其實很簡單:presenter持有一個 Observable<Integer> 做爲Model代替以前構造器中所須要的ShoppingCart

public class SelectedCountPresenter extends MviBasePresenter<SelectedCountView, Integer> {

  private Observable<Integer> selectedCountObservable;

  public SelectedCountPresenter(Observable<Integer> selectedCountObservable) {
    this.selectedCountObservable = selectedCountObservable;
  }

  @Override protected void bindIntents() {
    subscribeViewState(selectedCountObservable, SelectedCountToolbarView::render);
  }
}
複製代碼

There you go (原文爲法語,大概意思是「就是這樣」),每當咱們須要顯示當前選擇的條目數量時,咱們就可使用SelectedCountToolbar組件——這能夠表明ShoppingCart中的條目數,也能夠表示在App中的徹底不一樣的上下文環境和頁面中。

此外,此UI組件能夠放入獨立的庫中,並在另外一個App(如相冊應用程序)中使用,以顯示所選照片的​​數量:

Observable<Integer> selectedCount = photoManager.getPhotos()
    .map(photos -> {
       int selected = 0;
       for (Photo item : photos) {
         if (item.isSelected()) selected++;
       }
       return selected;
    });

return new SelectedCountToolbarPresnter(selectedCount);
複製代碼

結語

本文的目的是證實一般狀況下,代碼的設計中根本不須要 父子關係 ,它們僅須要經過簡單的對相同業務邏輯進行觀察就能實現。

不須要EventBus,不須要從上層的Activity或者Fragment中調用findViewById(),不須要presenter.getParentPresenter()或者其它的解決方案。僅使用 觀察者模式 就夠了。藉助於RxJava——它自己也是基於觀察者模式思想的體現,咱們就可以垂手可得構建這樣響應式的UI組件。

額外的思考

MVPMVVM相比,MVI的實現過程當中,咱們被迫(經過積極的方式)使用業務邏輯驅動某個組件的狀態。所以,具備更多MVI經驗的開發人員能夠得出如下結論:

若是View的狀態是另外一個組件的Model怎麼辦?若是一個組件的ViewState的變動是另外一個組件的Intent怎麼辦?

舉個例子:

Observable<Integer> selectedItemCountObservable =
        shoppingBasketPresenter
           .getViewStateObservable()
           .map(items -> {
              int selected = 0;
              for (ShoppingCartItem item : items) {
                if (item.isSelected()) selected++;
              }
              return selected;
            });

Observable<Boolean> doSomethingBecauseOtherComponentReadyIntent =
        shoppingBasketPresenter
          .getViewStateObservable()
          .filter(state -> state.isShowingData())
          .map(state -> true);

return new SelectedCountToolbarPresenter(
              selectedItemCountObservable,
              doSomethingBecauseOtherComponentReadyIntent);
複製代碼

乍一看,這彷佛是一種可行的方案,但它不是父子關係的變體嗎?固然不是,這並不是傳統分層的父子關係,也許將其比喻爲洋蔥更爲恰當(洋蔥的內層爲外層提供了一種狀態)。

可是,這依然是一種耦合的關係,不是嗎?我尚未下定決心,但如今我認爲避免這種洋蔥般的關係更好。若是您有不一樣意見,請在下面留言,我很期待您的觀點。


系列目錄

《使用MVI打造響應式APP》原文

《使用MVI打造響應式APP》譯文

《使用MVI打造響應式APP》實戰


關於我

Hello,我是卻把清梅嗅,若是您以爲文章對您有價值,歡迎 ❤️,也歡迎關注個人博客或者Github

若是您以爲文章還差了那麼點東西,也請經過關注督促我寫出更好的文章——萬一哪天我進步了呢?

相關文章
相關標籤/搜索