#####简介
分别使用MVC,MVP,MVP+VM,实践具体需求,对比优劣,逐步优化。
#####需求
实现我的押金页面,包含未缴纳,已缴纳,免押金3种状态 1.顶部title:3种状态展示不同文案; 2.金额:已缴纳,未缴纳状态金额字号,色值不同;免押金状态不展示; 3.底部tips:已缴纳,免押金状态展示不同文案;已缴纳状态,不展示; 4.按钮:未缴纳,已缴纳状态,文案,及点击事件都不相同;
#####MVC的实现方式 activity_main.xml
1 |
|
在MainActivity中通过butterKnife框架初始化view
1 |
|
定义IDepositRepository封装数据获取逻辑(server,sqlite),此处模拟网络请求
1 |
|
1 |
|
1 |
|
MyDepositModel用于存储数据
1 |
|
在MainActivity中调用IDepositRepository请求数据,通过OnDepositLoadListener获取请求成功后的数据,根据数据展示不同的view
1 |
|
效果图
项目结构: model:MydepositModel作为数据的载体,Repository负责从网络获取数据,两者共同承担着model的职责; view:activity_main.xml负责view的展示形式; control:MainActivity负责接收view的交互请求,提交给model;当model发生变化时操作view,更新展示逻辑。
Activity:view的容器,控制生命周期,页面交互与事件处理 xml:view展示与布局 view逻辑:操作view进行更新,如setText,setVisible等 业务逻辑:model更新,根据返回数据,执行逻辑主线,如:已/未缴纳/已认证 Repository:数据中心(server,sqlite) model:存储数据 交互逻辑:用户操作view,产生事件与数据,反向传递给model进行处理,如setOnclick,或在EditText中输入内容提交server等
######总结: xml作为view层,控制能力太弱,如果要去动态的改变一个Textview的字号,色值,或者隐藏/显示一个按钮,这些都没办法在xml中做,只能把代码写在Activity中。MyDepositModel以后,需要根据isAuth,isDepositPaied等业务逻辑,控制view的展示。造成了Activity既是view层,又是controller层,导致代码膨胀,当业务复杂度继续增加时,一个Activity上千行代码是很常见的,大量逻辑参与其中,维护及代码阅读难度将不断提升。 view和model直接交互,如:mTvMoney.setText(model.moneyNeed),耦合较重,无法独立变化。mTvMoney作为一个Textview,只需要提供通过setText方法将String设置到TextView上进行展示的一种能力,至于这个String是从model1,还是model2中获取的,mTvMoney并不关心,而model作为数据源,也同样不需要关心当前是展示在mTvMoney上,还是mTvTips上。mBtnPayOrReturn按钮也是一样,只需提供一种点击响应的能力,至于点击后是操作缴纳押金,还是退还押金,mBtnPayOrReturn并不关心。
#####MVP的实现方式1
在view和model之间新增presenter作为沟通的桥梁,presenter从model获取数据后,更新view的展示,使得view和model之间没有耦合,也将业务逻辑从view上抽离出来。
实现MainPresenter,持有IMainView,IDepositRepository成员变量,获取数据,根据业务逻辑更新view的展示。
1 |
|
1 |
|
实现view层
1 |
|
1 |
|
######总结 presenter处理业务逻辑并更新view,Activity只提供基础的操作view的能力,2者互相独立,view与业务分离。 业务变化1:不管已缴纳,未缴纳,免押金任何状态tipsText都不显示,此时就不需要去修改Activity,直接在presenter设置mIMainView.setTipsTextVisible()即可; 业务变化2:新增一种状态,已实名认证,不过某些条件不满足,押金不能全免,只能减半,titleText显示”已认证,还需缴纳押金”,moneyText大号字体,红色,tipsText显示”押金已减半”,Button显示文案”补足押金”,这种场景下,就不用去修改Activity的任何代码,只要在presenter新增逻辑分支,根据view提供的能力进行更新即可。
1 |
|
1 |
|
业务变化3:presenter依赖的是IMainView,不管是MainActivity,还是Main1Activity,只要是实现了IMainView即可复用当前presenter。
当我们把业务逻辑抽取到presenter后,Activity基本上只剩下一些view的逻辑,真正实现了减负,变成了一个相对纯净的view。当我们需要修改view的逻辑时,就去找Activity,需要修改数据逻辑时,就去找Repository,修改业务逻辑时就去找presenter,每个模块职责分明。 缺点: 1.view与presenter之间交互过于频繁,Activity中都是一些setText,setVisibility等方法。这时很容易让人想到使用Databinding可以很好的简化这部分代码。
#####MVP的实现方式2
通过DataBinding实现model到view的单向绑定,减少view与model之间因频繁交互而产生的冗余代码。
在标签中引入data=MyDepositModel,presenter=IMainPresenter。当model变化时,通过data将数据映射到view上。当Button产生点击事件时交由presenter响应并处理。使用Databinding以后,开发流程上省略了findView,setView的过程,在写xml的时候就可以直接将model进行关联及映射。
1 |
|
将业务逻辑转移到MyDepositModel
1 |
|
MainPresenter不再与view频繁的交互,仅仅是作为view和model的连接器,主干逻辑更为清晰
1 |
|
MainActivity中不再需要fingViewById,也不用定义Textview,Button的成员变量,全部交由DataBinding进行处理,相较MVP的实现,MainActivity进一步简化
1 |
|
IMainView接口也不再需要提供那么多操作view的方法
1 |
|
问题:
1.xml中参杂了一些业务逻辑,如:data.isDepositPay,data.isAuth,xml中应该尽量只是简单的view逻辑,与业务逻辑隔离。
2.由于使用databinding是model->view的单向绑定,不得不将大部分逻辑搬移到model中,例如:MyDepositModel中即有数据处理逻辑,isDepositPay,isAuth(如果model中存在list
#####MVPVM的实现方式
通过viewModel作为model和view的适配层,model只负责数据存储
activity_main.xml中将原先的model.isDepositPay(),model.isAuth()改成viewModel.moneyTextVisible(),viewModel.moneyTextSizeLarge()等。在xml中依赖viewModel,只关心view显示/隐藏,字号变大/小,色值高亮/正常,至于什么情况下展示高亮,是否显示由viewModel中适配的model逻辑决定。
1 |
|
IMyDepositViewModel接口,定义view提供的能力
1 |
|
MyDepositBaseViewModel实现IMyDepositViewModel的默认展示逻辑
1 |
|
已缴纳押金时viewModel的展示逻辑
1 |
|
未缴纳押金时viewModel的展示逻辑
1 |
|
已认证时viewModel的展示逻辑
1 |
|
MainPresenter获取数据后,根据不同业务逻辑展示 MyDepositAuthViewModel,MyDepositPayViewModel,MyDepositNoPayViewModel。此处有点像设计模式中的策略模式,这3个viewModel就是view的不同展示策略的封装。
1 |
|
MainActivity.java,只做基本的数据请求,DataBinding初始化,toast提示等操纵。
1 |
|
如图:用户操作view,触发事件响应,通过presenter中转,传递给model进行数据处理,获取新数据后处理业务逻辑,并适配成不同状态的viewModel展示策略,view根据不同的viewModel进行更新。
总结: 从mvc到mvpvm,项目中类虽然变多了,不过模块之间职责更加明确清晰。大部分情况,使用mvp结合databinding就可以较好的对view和model进行解耦,且代码冗余较少,当然在页面逻辑简单的情况下,可能连Presenter都没有用上的必要。不过如果是类似本文中的需求,view状态相对复杂的情况下,最好还是经过一层viewModel适配,也可以释放model的压力,xml布局中只依赖抽象的IMyDepositViewModel(model->view的数据输入)和IMainPresenter(view->model的事件输出),不依赖具体。 本文并非按照传统的MVC,MVP,MVVM的路线实现架构,而是采用循序渐进的方式,在MVC中发现Activity过重,所以引入MVP,Presenter作为View和Model的中转,达到解耦的目的。后来发现Activity提供view能力时冗余代码过多,所以引入DataBinding,虽然代码简化了,不过xml中引入了部分业务逻辑,model中同时参杂数据处理逻辑和view展示逻辑,故而引入viewModel,将xml与model进一步解耦,同时减轻model负担,不过此时并不算是mvvm,本质上在mvp的基础上,引入vm,因此presenter的中转作用还在,所以才演变成了现在的mvpvm。同时强调下,架构无绝对的好坏与绝对的标准,大家应该在项目中根据实际场景选择最合适的架构方式。本文中如有说明,解释不到位的地方,还请指出,互相学习共勉。
最终版本项目地址:https://github.com/listen2code/Test_MVPVM