原文: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。
若是您以为文章还差了那么点东西,也请经过关注督促我写出更好的文章——万一哪天我进步了呢?