[译]使用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

若是您以为文章还差了那么点东西,也请经过关注督促我写出更好的文章——万一哪天我进步了呢?

相关文章
相关标签/搜索