从零开始的Android新项目3 - MVPVM in Action, 谁告诉你MVP和MVVM是互斥的

前言

去年5月左右的时候,笔者在逛GitHub的时候,看到了一个MVP的项目,叫做mosby,仔细看了源码和作者介绍的文章后,发现确实有点意思,虽然会需要多写几个类和方法,但是解决了activity/fragment过重的问题,通过V/P分离能够帮助提高可维护性。时至去年年底,今年年初,MVP才逐渐被大家所知,也不时看到些文章介绍其概念和实践。

再说说MVVM (Model-View-ViewModel),在Android上对应data binding。即ViewModel到View的映射,不需要再去自己找到view,然后更新字段,而是在映射建立后直接更新ViewModel然后反映到View上。

值得一提的是,MVP和MVVM都是微软提出的理念,最早都是在WPF里面被应用的,只是时至今日才在Android上被真正用起来。本文不是来介绍这两个的,所以不再赘述,讲讲正题。

在本系列首篇中我曾经提到过在我设计的新应用中,采用了MVP+MVVM的混合(当初也考虑过Flux,但感觉并不合适Android),后来有一次CJJ同学和我探讨这个架构的时候,问到了我有没有什么正式的名字,我就有楞,因为这个组合和应用是我自己设计的,所以还真没想过这个问题,Google一搜,还真有这么个东西(见参考资料,文章写得很棒,建议英文不错的同学读一读)!

这就是本文我要介绍的东西,MVPVM (Model-View-Presenter-ViewModel)。

Quick glance

以下所有Model,并不单单指的是Bean,而是Model层,更像是repository或者business logic。

MVC

MVC: View持有Controller,传递事件给Controller,Controller由此去触发Model层事件,Model更新完数据(如从网络或者数据库获得数据后)触发View的更新事件。

MVP

乍一看,MVP似乎是MVC的变种,即C的位置被P取代了,但如果我们再看一看下图:

MVCP

其实MVP是MVC的一个wrap,C层仍然可以在那里,代替View处理点击事件、数据绑定、扮演ListView的观察者,从而View可以专注于处理纯视觉的一些东西。而Presenter则避免了Model直接去触发View的更新,View彻底成为了一个被动的东西,只有Presenter告知其更新视觉,它才会去更新,比如showLoading(), showEmpty()。

MVVM

MVVM通过View和ViewModel的双向绑定,让我们可以

  • 直接更新ViewModel,View会进行对应刷新
  • View的事件直接传递到ViewModel,ViewModel去对Model进行操作并接受更新。

Why MVPVM

如果你仔细读过Clean architecture的源码,会发现其中已经有了ViewModel这一层。如果你熟悉DO(Domain Object),PO(Persistent Object),VO(View Object),或许了解visibility这个概念,各层只需要知道其应该知道的。这些Object代表了完全独立不同的概念。

ViewModel层的必要性,简单举个例子,服务器传来一个Date String,但我们需要显示的是该Date到现在的相对时间描述,比如1分钟前,2天前,为了避免在view中绑定数据时去做这个逻辑,ViewModel会代替来进行这个的转换。

MVVM尽管确实省去了绑定数据到View的boilerplate,但

  • ViewModel引用了View,从而导致ViewModel无法重用于其他View。
  • 并没有解决View层过重的问题,仅仅去掉了数据绑定,尤其对一些复杂业务逻辑的页面。

模式的引入都是为了通过可拔插化以提高可复用性,松耦合和尽量小的接口可以给予最大的可复用性,使得组件能重组使用。

所以有了MVPVM:

MVPVM

在我的个人实践中:

  • Model: data和domain模块组成,包含了Interactor(UseCase)、Repository、Datastore、Retrofit、Realm、DO、部分PO等。
  • View: Activity/Fragment。
  • Presenter:Presenter,包含了Subscriber,并通过Dagger2注入UseCase从而减轻耦合。
  • ViewModel:由Model转换而成,继承BaseObservable或SortedList,大部分直接wrap了model,从而去掉了mapper的boilerplate。通过Data Binding绑定到xml。

从Presenter的Subscriber往下都是RxJava的流世界,stream in stream out。如果你原来就应用了MVP或者Clean Architecture,那会发现再加上ViewModel简直太简单了,同时让代码库更小,逻辑更清晰。

接着看看各个组件在MVPVM中的standing。

MVPVM: Model

实际对应的是Repository层,即第一篇文章中提到的data/domain module。具体的Model理论上应该是PO,但我们大部分场景并不需要PO,所以也可以是domain层的DO。

MVPVM: View

View对ViewModel不需要了解太多,这样才能保持两者的解耦,两者之间的协议只需要:

  • ViewModel支持View需要展示的properties。
  • View实现了ViewModel的观察者模式接口(如Listener)。

所以这里ViewModel到View是一条虚线,而不是MVVM中的双向实线。

MVPVM: Presenter

和在MVP一样,Presenter站在View和Model层之间。这里值得一提的是Presenter到ViewModel是有耦合的,因为Presenter需要把model更新到ViewModel中,也就是map行为,然后调用View的对应接口进行binding。

Presenter是MVPVM中唯一不需要解耦的,它紧紧地与View、ViewModel、Model层耦合。如果你的Presenter被多个View重用了,那你可能需要考虑它是不是更应该作为一个module,比如(第三方)登陆。

MVPVM: ViewModel

MVPVM让ViewModel可以重用,因为它再也不是直接和特定View绑定,而仅仅作为数据到View的一个绑定用展示。ViewModel因为用户操作而触发的事件不再直接对Model进行操作,而由View去负责任务流。ViewModel本身基本没有field,而是通过暴露get方法来让data binding找到对应要显示的property,get方法中直接调用持有的model的对应属性get方法。

理想化的架构是通过一个mapper类进行转换,但我想大部分的程序员面对这个工作都会抓狂,毕竟很多字段其实就是一个复制,而且对性能也有一些影响(遍历list,new对象,一个个字段转换,添加到新的list)。所以折中地,让ViewModel持有Model,在get方法中直接返回对应model的具体字段,在一些特殊的field如相对时间、添加一些描述性字符的地方再去进行拼接和特殊处理。

啊,对了,说到ViewModel,Data Binding现在支持双向绑定了哦,见https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/,语法如:

<EditText android:text="@={user.firstName}" .../>

不同于单向绑定的@{},使用了@={},毕竟双向绑定这个东西还是慎用,一方面早成数据流混乱不好理解,另一方面容易出现死循环。

NO Presenter

在MVP中,我们有时候碰到的问题是,Presenter真的有必要存在吗,尤其是一些较为静态,没什么业务逻辑,只需要纯展示的页面,结果就是为了MVP而特意去创建一个Presenter。

所以Presenter不应该被强求,正如MVP中,V和C其实被并在了一起,在某些情况下(确实就是个纯展示,或者很少的业务逻辑),应该允许去Presenter,并让View承担其任务。比如注册页面,我真的就只是想把用户的输入发到服务器验证一下,何必非得去搞一个presenter套着呢?

我们不能永远理想化地去选择所谓最好的设计,在现实的必要情况下,我们要敢于舍弃,最合适的设计才是最好的设计。为此,Presenter不是强制的;为此,ViewModel并不一定通过mapper生成,而可以返回持有的DO对象对应字段。

总结

本篇讲了讲MVPVM及其在Android的实践,因为时间原因来不及写个demo来说说具体实现,欢迎大家提出意见和建议。有空的话我最近会在GitHub上写一下demo,你如果有兴趣可以follow一下等等更新: markzhai

下集预告

Dagger匕首,比ButterKnife黄油刀锋利得多。Square为什么这么有自信地给它取了这个名字,Google又为什么会拿去做了Dagger2呢?笔者看了很多国内Dagger2的文章,但发现它们都保留在介绍API和官网翻译的层面,无法让读者能明白究竟为什么用Dagger2,又如何用好Dagger2。希望能在下一次为大家讲清楚。

参考资料

原文:http://blog.zhaiyifan.cn/2016/03/14/android-new-project-from-0-p3/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容