原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS
做者:Hannes Dorfmann
譯者:卻把清梅嗅java
這篇博客中,咱們將針對如何 如何構建獨立組件 進行探討,我將闡述爲何在我看來 父子關係會致使壞味道的代碼,以及爲什麼這種關係是沒有意義的。android
有這樣一個問題時不時涌如今個人腦海中—— MVI
、MVP
、MVVM
這些架構設計模式中,多個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
:SelectedCountPresenter
和 ShoppingBasketPresenter
。這屬於父子關係嗎?不,它們僅僅是觀察了同一個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 的代碼和上述代碼的實現很是相似,所以本文不對其進行展現。然而,若是咱們認真去觀察這段代碼,你會發現SelectedCountPresenter
和ShoppingCart
有必定的耦合。
咱們徹底有可能會在其它的頁面去複用這個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組件。
與MVP
或MVVM
相比,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。
若是您以爲文章還差了那麼點東西,也請經過關注督促我寫出更好的文章——萬一哪天我進步了呢?