[译]使用 MODEL-VIEW-INTENT 第四部分 — 独立 UI 组件

在这篇博客我们将讨论如何构建独立UI组件,并且要弄清楚为什么在我看来子类和父类关系充满着坏代码的味道。此外,我们将讨论为什么我认为这种关系是不必要的。

不时的出现诸如 Model-View-Intent,Model-View-Presenter 或 Model-View-ViewModel 之类的架构设计模式的一个问题是,Presenter(或ViewModels) 之间是如何通信的?甚至更具体一点,"子-Presenter"如何与它的"父-Presenter"进行沟通?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

父子关系的组件充满着代码异味,因为它们表示了一种父类与子类的​
直接耦合,这就导致了代码很难阅读,很难维护,当需求发生变化会影响很多组件(尤其是在大型系统中几乎是不可能完成的任务)最后,同样重要的是,引入了很多很难预测甚至更难去复刻和调试的共享状态。

到现在为止还挺好的,但是我们假设信息必须从 Presenter A 流向 Presenter B:如何让不同的 Presenter 相互间通信? 它们不通信!什么样的场景才需要一个 Presenter 不得不与另一个 Presenter 通信?事件 X 发生了?Presenters 完全不用相互间通信,他们仅仅观察相同的 Model(或者精确到相同的业务逻辑)。这是它们如何得到关于变化的通知:从底层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

无论何时一个事件X发生了(例如:一个用户点击了在View1上的按钮), 这个 Presenter 会让信息下沉到业务逻辑。既然其他的 Presenter 观察相同的业务逻辑, 他们从已经变化的业务逻辑(model 已经发生变化)里得到通知。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们已经在第一部分强调了一个很重要的原则(单向数据流)。

让我们用真实案例来实现上面的内容:在我们的电商 app 我们可以将任意一项商品放到购物车里。另外,这里还有一个页面,我们可以看到我们购物车的所有商品,并且我们一次性可以选择或者移除多个商品项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们可以把这个大的页面分离成很多小的,独立的并且可复用的UI组件,那岂不是很酷?比如说一个 Toolbar,它显示被选择的 item 的数量,和一个用来显示购物车里的商品项列表的 RecyclerView。

<com.hannesdorfmann.ShoppingBasketRecyclerView
android:id=“@+id/shoppingBasketRecyclerView”
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“1”
/>

但是如何使这些组件进行相互间通信呢?显然每个组件有它自己的 Presenter:selectedCountPresentershoppingBasketPresenter。这是父子关系吗?不,两者都仅仅观察同一个 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:

public class SelectedCountPresenter
extends MviBasePresenter<SelectedCountView, Integer> {

private Observable selectedCountObservable;

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

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

就是这样,任何时候,当我们想要显示当前 item 选择数量的时候,我们可以用这个 SelectedCountToolbar 组件。这个组件在购物车,可以记物品项的数量。但是,这个 UI 控件也可以用在你 App 里完全不同的情景下。此外,这个 UI 控件可以放在一个独立库中,并且在其他的 app 中使用,比如一个能显示选择多少张照片的 app。

Observable 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 selectedItemCountObservable =

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
img-zoFEri8p-1719094092908)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值