使用 MODEL-VIEW-INTENT 第四部分 — 独立 UI 组件 (#3361)

* 使用MODEL-VIEW-INTENT-第四部分-独立UI组件

使用MODEL-VIEW-INTENT-第四部分-独立UI组件

* Update mosby3-mvi-4.md

修改

* Update mosby3-mvi-4.md
This commit is contained in:
pcdack
2018-03-05 13:12:33 +08:00
committed by LeviDing
parent 4cb03d36ea
commit 03d172e3c6
+28 -24
View File
@@ -2,34 +2,34 @@
> * 原文作者:[Hannes Dorfmann](http://hannesdorfmann.com/)
> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO/mosby3-mvi-4.md](https://github.com/xitu/gold-miner/blob/master/TODO/mosby3-mvi-4.md)
> * 译者:
> * 校对者:
> * 译者:[pcdack](https://github.com/pcdack)
> * 校对者:[hanliuxin5](https://github.com/hanliuxin5)
# REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS
# 使用 MVI 开发响应式 APP — 第四部分 — 独立的UI组件
In this blog post we will discuss how to build independent UI components and clarify why Parent-Child relations are a code smell in my opinion. Furthermore, we will discuss why I think such relations are needless.
在这篇博客我们将讨论如何构建独立UI组件,并且要弄清楚为什么在我看来子类和父类关系充满着坏代码的味道。此外,我们将讨论为什么我认为这种关系是不必要的。
One question that arises from time to time with architectural design patterns such as Model-View-Intent, Model-View-Presenter or Model-View-ViewModel is how do Presenters (or ViewModels) communicate with each other? Or even more specific: How does a “Child-Presenter” communicate with its “Parent-Presenter”?
不时的出现诸如 Model-View-IntentModel-View-Presenter Model-View-ViewModel 之类的架构设计模式的一个问题是,Presenter(或ViewModels) 之间是如何通信的?甚至更具体一点,"子-Presenter"如何与它的"父-Presenter"进行沟通?
![wtf](http://hannesdorfmann.com/images/mvi-mosby3/wtf.jpg)
From my point of view such Parent-Child relations are a code smell, because they introduce a direct coupling between both Parent and Child, which leads to code that is hard to read, hard to maintain, where changing requirement affects a lot of components (hence its a virtually impossible task in large systems) and last but not least introduces shared state that is hard to predict and even harder to reproduce and debug.
父子关系的组件充满着代码异味,因为它们表示了一种父类与子类的直接耦合,这就导致了代码很难阅读,很难维护,当需求发生变化会影响很多组件(尤其是在大型系统中几乎是不可能完成的任务)最后,同样重要的是,引入了很多很难预测甚至更难去复刻和调试的共享状态。
So far so good, but somehow the information must flow from Presenter A to Presenter B: How does a Presenter communicate with another Presenter? **They dont!** What would a Presenter have to tell another Presenter? _Event X_ has happened? Presenters dont have to talk to each other, they just observe the same Model (or the same part of the business logic to be precise). Thats how they get notified about changes: from the underlying layer.
到现在为止还挺好的,但是我们假设信息必须从 Presenter A 流向 Presenter B:如何让不同的 Presenter 相互间通信? **它们不通信**!什么样的场景才需要一个 Presenter 不得不与另一个 Presenter 通信?事件 X 发生了?Presenters 完全不用相互间通信,他们仅仅观察相同的 Model(或者精确到相同的业务逻辑)。这是它们如何得到关于变化的通知:从底层。
![Presenter-Businesslogic](http://hannesdorfmann.com/images/mvi-mosby3/mvp-business-logic.png)
Whenever an _Event X_ happens (i.e. a user clicked on a button in View 1), the Presenter lets that information sink down to the business logic. Since the other Presenters are observing the same business logic, they get notified by the business logic that something has changed (model has been updated).
无论何时一个事件X发生了(例如:一个用户点击了在View1上的按钮), 这个 Presenter 会让信息下沉到业务逻辑。既然其他的 Presenter 观察相同的业务逻辑, 他们从已经变化的业务逻辑(model 已经发生变化)里得到通知。
![Presenter-Businesslogic](http://hannesdorfmann.com/images/mvi-mosby3/mvp-business-logic2.png)
We have already discussed the importance of this principle (unidirectional data flow) in the [first part](http://hannesdorfmann.com/android/mosby3-mvi-1).
我们已经在[第一部分](https://juejin.im/post/5a52e4445188257334228b28)强调了一个很重要的原则(单向数据流)。
Lets implement this for a real world example: In our shopping app we can put items into the shopping basket. Additionally, there is a screen where we can see the content of our basket and we can select and remove multiple items at once.
让我们用真实案例来实现上面的内容:在我们的电商 app 我们可以将任意一项商品放到购物车里。另外,这里还有一个页面,我们可以看到我们购物车的所有商品,并且我们一次性可以选择或者移除多个商品项。
<iframe width="894" height="503" src="https://www.youtube.com/embed/ZvnceMj8NoY" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
![](https://i.loli.net/2018/03/02/5a98f0759859f.gif)
Wouldnt it be cool if we could split that big screen into multiple smaller, independent and reusable UI components. Lets say a Toolbar, that displays the number of items that are selected, and a RecyclerView that actually displays the list of items in the shopping basket.
如果我们可以把这个大的页面分离成很多小的,独立的并且可复用的UI组件,那岂不是很酷?比如说一个 Toolbar,它显示被选择的 item 的数量,和一个用来显示购物车里的商品项列表的 RecyclerView。
```
<LinearLayout>
@@ -48,7 +48,7 @@ Wouldnt it be cool if we could split that big screen into multiple smaller, i
</LinearLayout>
```
But how do these components communicate with each other? Obviously each component has its own Presenter: **SelectedCountPresenter** and **ShoppingBasketPresenter**. Is that a Parent-Child relation? No, both are just observing the same Model (updated from the same business logic):
但是如何使这些组件进行相互间通信呢?显然每个组件有它自己的 Presenter:**selectedCountPresenter**和**shoppingBasketPresenter**。这是父子关系吗?不,两者都仅仅观察同一个 Model(从相同的业务逻辑里获取更新):
![ShoppingCart-Businesslogic](http://hannesdorfmann.com/images/mvi-mosby3/shoppingcart-businesslogic.png)
@@ -82,7 +82,7 @@ class SelectedCountToolbar extends Toolbar implements SelectedCountView {
}
```
The code for **ShoppingBasketRecyclerView** looks pretty much the same and therefore we skip that here. However, if we take a closer look at **SelectedCountPresenter** we notice that this Presenter is coupled to **ShoppingCart**. We would like to use the UI component also on other screens in our app. To make that component reusable we have to remove this dependency, which is actually an easy refactoring: The presenter gets an **Observable<Integer>** as Model through the constructor instead of ShoppingCart:
**ShoppingBasketRecyclerView** 的代码看起来不错,有很多相同的地方,因此我忽略掉这些相同的地方了。然而,如果我们仔细观察 **selectedCountPresenter** 我们会注意到这个 Presenter 与 **shoppingcart** 耦合。我们想要使用这个 UI 组件可以在我们 App 的其他的页面使用,让这个组件变的可复用,我们需要移除这个依赖,这事实上是一个简单的重构:这个 Presenter 得到一个 **Observable<Integer>** 作为 Model 的构造函数取代原来的 ShoppingCart:
```
public class SelectedCountPresenter
@@ -100,7 +100,7 @@ public class SelectedCountPresenter
}
```
Et voilà, we are able to use the SelectedCountToolbar component whenever we have to display the number of items currently selected. That can be the number of items in ShoppingCart but this UI component could also be used in an entirely different context and screen in your app. Moreover, this UI component could be put into a standalone library and used in another app like a photos app to display the number of selected photos.
就是这样,任何时候,当我们想要显示当前 item 选择数量的时候,我们可以用这个 SelectedCountToolbar 组件。这个组件在购物车,可以记物品项的数量。但是,这个 UI 控件也可以用在你 App 里完全不同的情景下。此外,这个 UI 控件可以放在一个独立库中,并且在其他的 app 中使用,比如一个能显示选择多少张照片的 app。
```
Observable<Integer> selectedCount = photoManager.getPhotos()
@@ -115,17 +115,17 @@ Observable<Integer> selectedCount = photoManager.getPhotos()
return new SelectedCountToolbarPresnter(selectedCount);
```
## Conclusion
## 总结
The aim of this blog post is to demonstrate that a Parent-Child relation is usually not needed at all and can be avoided by simply observing the same part of your business logic. No EventBus, no findViewById() from a parent Activity / Fragment, no presenter.getParentPresenter() or other workarounds are required. Just the observer pattern. With the help of RxJava, which basically implements the observer pattern, we are able to build such reactive UI components easily.
这篇博客的目的是为了演示,父子关系通常来说是不需要的,并且可以避免,通过简单的观察你业务逻辑的相同部分。 不用 EventBus, 不需要从你的父 Activity/Fragment 中 findViewById(),不需要Presenter.getParentPresenter() 或者其他需要其他的解决办法。仅仅需要观察者模式。伴有 RxJava 的帮助,RxJava 是实现观察者模式的基础,我们可以很轻松的构建这样的响应式 UI 组件。
### Additional thoughts
### 另外的思考
In contrast to MVP or MVVM in MVI we are forced (in a positive way) that business logic drives the state of a certain component. Hence developers with more experience in MVI could come to the following conclusion:
通过与 MVP 或者 MVVM 的对比,在 MVI 我们强制(用一种激进的方法)让业务逻辑驱动一定的组件状态。故在使用 MVI 上有经验的开发者总结出下面结论:
> What if such a view state is the model of another component? What if a view state change of one component is an intent for another component?
> 如果一个 view 状态是另一个组件的 model?如果 view 的状态在一个组件中发生了变化,这个变化是另一个组件的意图,那么如何处理?
Example:
例子:
```
Observable<Integer> selectedItemCountObservable =
@@ -150,10 +150,10 @@ return new SelectedCountToolbarPresenter(
doSomethingBecauseOtherComponentReadyIntent);
```
At first glance this seems like a valid approach, but isnt it a variant of a Parent-Child relation? Sure, its not a traditional hierarchical Parent-Child relation, its more like an onion (the inner one offers a state to the outer one) which seems to be better, but still, a tightly coupled relation, isnt it? I havent made up my mind but I think avoiding this onion-like relation is better for now. If you have a different opinion please leave a comment below. I would love to hear your thoughts.
乍一看这似乎是一种有效的方法,但它不是父子关系的变体吗? 当然,这不是一个传统的分层父子关系,它更像是一个洋葱(内部的给外部状态),这似乎更好,但仍然是一个紧密耦合的关系,不是吗?我还没有下定决心,但我认为现在避免这种类似洋葱的关系更好。 如果您有不同的意见,请在下面留言。 我很想听听你的想法。
**This post is part of the blog post series "Reactive Apps with Model-View-Intent".
Here is the Table of Content:**
**这篇博客是“用 MVI 开发响应式App”的一部分。
下面是内容表:**
* [Part 1: Model](http://hannesdorfmann.com/android/mosby3-mvi-1)
* [Part 2: View and Intent](http://hannesdorfmann.com/android/mosby3-mvi-2)
@@ -163,6 +163,10 @@ Here is the Table of Content:**
* [Part 6: Restoring State](http://hannesdorfmann.com/android/mosby3-mvi-6)
* [Part 7: Timing (SingleLiveEvent problem)](http://hannesdorfmann.com/android/mosby3-mvi-7)
**这是这个系列博客的中译版:**
* [第一部分:Model](https://juejin.im/post/5a52e4445188257334228b28)
* [第二部分:View 和 Intent](https://juejin.im/post/5a587c06518825732f7eab86)
* [第三部分:状态折叠器](https://juejin.im/post/5a955c50f265da4e853d856a)
---