- 原文地址:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS
- 原文做者:Hannes Dorfmann
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:pcdack
- 校對者:hanliuxin5
在這篇博客咱們將討論如何構建獨立UI組件,而且要弄清楚爲何在我看來子類和父類關係充滿着壞代碼的味道。此外,咱們將討論爲何我認爲這種關係是沒必要要的。前端
不時的出現諸如 Model-View-Intent,Model-View-Presenter 或 Model-View-ViewModel 之類的架構設計模式的一個問題是,Presenter(或ViewModels) 之間是如何通訊的?甚至更具體一點,"子-Presenter"如何與它的"父-Presenter"進行溝通?java
父子關係的組件充滿着代碼異味,由於它們表示了一種父類與子類的直接耦合,這就致使了代碼很難閱讀,很難維護,當需求發生變化會影響不少組件(尤爲是在大型系統中幾乎是不可能完成的任務)最後,一樣重要的是,引入了不少很難預測甚至更難去復刻和調試的共享狀態。android
到如今爲止還挺好的,可是咱們假設信息必須從 Presenter A 流向 Presenter B:如何讓不一樣的 Presenter 相互間通訊? 它們不通訊!什麼樣的場景才須要一個 Presenter 不得不與另外一個 Presenter 通訊?事件 X 發生了?Presenters 徹底不用相互間通訊,他們僅僅觀察相同的 Model(或者精確到相同的業務邏輯)。這是它們如何獲得關於變化的通知:從底層。ios
不管什麼時候一個事件X發生了(例如:一個用戶點擊了在View1上的按鈕), 這個 Presenter 會讓信息下沉到業務邏輯。既然其餘的 Presenter 觀察相同的業務邏輯, 他們從已經變化的業務邏輯(model 已經發生變化)裏獲得通知。git
咱們已經在第一部分強調了一個很重要的原則(單向數據流)。github
讓咱們用真實案例來實現上面的內容:在咱們的電商 app 咱們能夠將任意一項商品放到購物車裏。另外,這裏還有一個頁面,咱們能夠看到咱們購物車的全部商品,而且咱們一次性能夠選擇或者移除多個商品項。後端
若是咱們能夠把這個大的頁面分離成不少小的,獨立的而且可複用的UI組件,那豈不是很酷?好比說一個 Toolbar,它顯示被選擇的 item 的數量,和一個用來顯示購物車裏的商品項列表的 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:selectedCountPresenter和shoppingBasketPresenter。這是父子關係嗎?不,二者都僅僅觀察同一個 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 的代碼看起來不錯,有不少相同的地方,所以我忽略掉這些相同的地方了。然而,若是咱們仔細觀察 selectedCountPresenter 咱們會注意到這個 Presenter 與 shoppingcart 耦合。咱們想要使用這個 UI 組件能夠在咱們 App 的其餘的頁面使用,讓這個組件變的可複用,咱們須要移除這個依賴,這事實上是一個簡單的重構:這個 Presenter 獲得一個 Observable 做爲 Model 的構造函數取代原來的 ShoppingCart:app
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);
}
}
複製代碼
就是這樣,任什麼時候候,當咱們想要顯示當前 item 選擇數量的時候,咱們能夠用這個 SelectedCountToolbar 組件。這個組件在購物車,能夠記物品項的數量。可是,這個 UI 控件也能夠用在你 App 裏徹底不一樣的情景下。此外,這個 UI 控件能夠放在一個獨立庫中,而且在其餘的 app 中使用,好比一個能顯示選擇多少張照片的 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 的幫助,RxJava 是實現觀察者模式的基礎,咱們能夠很輕鬆的構建這樣的響應式 UI 組件。
經過與 MVP 或者 MVVM 的對比,在 MVI 咱們強制(用一種激進的方法)讓業務邏輯驅動必定的組件狀態。故在使用 MVI 上有經驗的開發者總結出下面結論:
若是一個 view 狀態是另外一個組件的 model?若是 view 的狀態在一個組件中發生了變化,這個變化是另外一個組件的意圖,那麼如何處理?
例子:
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」的一部分。 下面是內容表:
這是這個系列博客的中譯版:
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。