MVX、Clean总结
MVC/MVP/MVVM/MVI
MVC
什么是 MVC?
MVC 其实是 Android 默认的设计。
- M: Model 数据类(数据的获取、存储、数据状态变化)
- V: View Layout XML 文件
- C: Controller Activity 负责处理数据、业务和 UI
MVC 流程
- View 接收用户的交互请求,View 将请求转交给 Controller
- Controller 操作 Model 进行数据更新,数据更新之后,Model 通知 View 数据变化
- View 显示更新之后的数据
MVC 缺点
Activity 责任不明、十分臃肿
Activity 由于其生命周期的功能,除了担任 View 层的部分职责(加载应用的布局、接受用户操作),还要承担 Controller 层的职责(业务逻辑的处理)
随着界面的增多 & 逻辑复杂度提高,Activity 类的代码量不断增加,越加臃肿
MVP
什么是 MVP?
MVP 是 MVC 架构的一个演化版,解决 MVC 中 Activity 作为 V 和 C 的问题:将 Activity/Fragment 中的业务逻辑分离出来,Activity/Fragment 只做 View 展示用,业务逻辑移动到 Presenter 层。
- M Model:实体类(数据的获取、存储、数据状态变化)
- V View:Activity/Fragment/Layout XML 文件
- P Presenter:负责 View 和 Model 之间的交互和业务逻辑
一般地,V 和 P 中间会定义一个契约层 Contract,规定 V 如何向 P 发指令和 P 如何 Callback 给 V 层。
MVP 流程
- View 接收用户交互请求,View 将请求转交给 Presenter(V 调用 P 接口)
- Presenter 操作 Model 进行数据更新(P 调用 M 接口)
- Model 通知 Presenter 数据发生变化(M 调用 P 接口)
- Presenter 更新 View 数据(P 执行 View 的接口方法,也就调用了 V 的实现类)
MVP 优缺点
优点
- 负责的逻辑处理放在 Presenter 进行处理,减少了 Activity 的臃肿
- M 和 V 解耦,Model 和 View 层完全分离,修改 View 层不会影响 Model 层
- 可以将一个 Presenter 用于多个视图,而不需要改变 Presenter 的逻辑
- Presenter 层和 View 层的交互是通过接口来进行的,便于单元测试
缺点
- 双向依赖:V 和 P 是双向依赖的,一旦 View 层做出改变,相应的 P 也需要做出调整,V 层变化是大概率事件
- 内存泄漏风险:P 持有了 V 层的引用,当用户关闭 V 层,P 层如果在子线程进行耗时操作,存在内存泄漏的风险,可以利用 Lifecycle 解决
- 协议接口类膨胀:V 和 P 交互需要定义接口,当交互复杂时,需要定义很多接口方法和回调方法,不好维护
MVVM
是什么 MVVM?
MVVM 与 MVP 的结构很相似的,就是将 Presenter 升级为 ViewModel。
- View:Activity/Fragment 和 Layout XML 文件,与 MVP 中 View 的概念相同;
- Model:负责管理业务数据逻辑(如网络请求、数据库处理),与 MVP 中的 Model 概念相同
- ViewModel:存储视图状态,负责处理表现逻辑,并将数据设置给可观察数据容器
实现细节上,V 和 P 从双向依赖变成 V 可以向 VM 发送指令,但 VM 不会直接向 V 回调,而是让 V 通过 _ 观察者的模式 _ 去监听 VM 层数据的变化,有效的规避了 MVP 中 V 和 P 双向依赖的问题。
DataBinding、ViewModel 和 LiveData 等组件是 Google 为了帮助我们实现 MVVM 模式提供的架构组件,它们并不是 MVVM 的本质,只是实现上的工具。
MVVM 存在的缺点
- 多数据流:View 和 VewModel 的交互分散,缺少唯一的修改源,不易于追踪
- LiveData 膨胀:复杂的页面需要定义多个 MutablLiveData,并且都需要暴露为不可变的 LiveData。
1
2
3
4
5
6
7
8
9
10
class TestViewModel : ViewModel() {
// 为保证对外暴露的LiveData不可变,增加一个状态就要添加两个LiveData变量
private val _pageState: MutableLiveData<PageState> = MutableLiveData()
val pageState: LiveData<PageState> = _pageState
private val _state1: MutableLiveData<String> = MutableLiveData()
val state1: LiveData<String> = _state1
private val _state2: MutableLiveData<String> = MutableLiveData()
val state2: LiveData<String> = _state2
//...
}
- 不用 DataBinding 的理由 我不使用Android DataBinding的四个原因
- JakeWharton 不推荐使用
- xml 里面写业务逻辑,难以维护
- 只适合用于简单的 app,
MVI
什么是 MVI?
MVI 和 MVVM 相似,借鉴了前框框架的思想,更加强调数据的单向流动和唯一数据源。
- Model:与 MVVM 的 Model 不同的是,MVI 的 Model 主要是指 UI 状态 State,并暴露出 ViewState 供 ViewState 订阅,ViewState 一般是个 data class 包含所有页面状态。(如页面加载状态、控件位置等)
- View:与其他 MVX 的 ViewState 一样,通常为 Activity 或 View;View 通过订阅 Model 变化实现界面刷新
- Intent:或者叫 Action,指用户的操作包装成 Intent 发送给 Model 层进行数据请求
MVI 交互流程
- 用户操作以 Intent(Action) 的形式通知 Model
- Model 基于 Intent 更新 State
- View 接收到 State 变化刷新 UI
在实现细节上,View 和 ViewModel 之间的多个交互(多 LiveData 数据流)变成了单数据流。无论 View 有多少个视图状态,只需要订阅一个 ViewState 便可以获取所有状态
MVI 优缺点
优点:
- 强调数据单向流动,很容易对状态变化进行跟踪和回溯
- State 是单一信息源(全局状态管理),不用担心各个 View 的状态到处都是;使用 ViewState 对 State 集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态(MVVM 是多个 State)
缺点:
- **State 膨胀 **所有视图变化都转换为 ViewState(可以划分成几个小的分状态来缓解)
- **局部刷新 **View 根据 ViewState 响应,不易实现局部 Diff 刷新(自定义 LiveData 响应局部属性;Flow.distinctUntilChanged() 来减少不必要的刷新)
- Event 的黏性事件问题:LiveData 和 StateFlow 都有黏性问题
MVVM 和 MVI 的对比
- 对于新的团队,一般都熟悉 MVVM,MVVM 架构的接受度肯定会好
- 对于复杂的场景,采用 MVI 的全局状态管理思路更优
- MVVM 和 MVI 的复杂度对比
- MVVM
- View 发起请求的复杂度:ViewModel 的各种方法调用会散落在界面不同地方(即界面向 ViewModel 发起请求没有统一入口)
- View 观察数据的复杂度:界面需要观察多个 ViewModel 提供的数据,导致界面状态的一致性难以维护
- ViewModel 内部请求和数据关系的复杂度:数据被定义为 ViewModel 的成员变量。成员变量是增加复杂度的利器,因为它可以被任何成员方法访问。也就是说,新增业务对成员变量的修改可能影响老业务的界面展示。同理,当界面展示出错时,也很难一下子定位到是哪个请求造成的。
- MVVM
- MVI:解决了上面的三个问题
- State 单一数据源
- Event 需要消除黏性,SharedFlow/SingleLiveEvent
Clean
分层
分三层,每层都有自己的数据模型,每层之间通过接口方式来通信
Presentation Layer
也就是 MVX 结构所对应的地方(MVC、MVP 等),这里不处理 UI 以外的任何逻辑。
Domain Layer
业务逻辑,use case 实现的地方,在这里包含了 use case(用例) 以及 Business Objects(业务对象),按照洋葱图的依赖规则,这层属于最内层,也就是完全不依赖于外层。
Data Layer
所有 APP 需要的数据
Viper?
货拉拉提到