1.总序
MVC/MVP/MVVM三者的区别
个人觉得,这三种设计框架最大的区别是:是否区分业务展示和业务逻辑,这就包含事件处理归谁所有以及事件通知方式(这也涉及到数据的单向绑定和双向绑定问题)。
MVC:1)不区分业务展示和业务逻辑,全部有C层负责,所以不方便单元测试,故有MVP的架构出现,而并非有的人说的MVP/MVVM出现是为了C的瘦身,MVC用的得当,自然可实现C瘦身。
2)C自己处理一切数据和操作然后展现到V上
MVP:1)区分业务展示和业务逻辑,业务逻辑转移到P层,方便单元测试,分层会让代码逻辑优点绕, 同时也带来了大量的代码工作,这就有了MVVM的架构出现
2)V/C委托P处理数据或操作, 然后P会将处理/操作的结果告诉V/C, 最后展示到V上,数据是单向的。MVP和MVVM他们都是区分业务展示和业务逻辑的,方便测试,他们最大的区别,就是主动性或者说知晓性。 MVP中业务变化时P层是主动通知V/C层(像delegate,block), 即P是绝对知道有V/C层的存在的, 只是不知道V/C的具体类型. 而MVVM中VM则不曾主动通知过谁, 而是由V/C观察/订阅VM的变化然后自行处理相应情况(像noticfaction), 从始至终VM都是不知道外界的V/C的存在的.
MVVM:1)区分业务展示和业务逻辑,方便单元测试。
2)V/C观察VM处理数据或操作, 并将观察到的结果展示到V上,数据是双向帮绑定。MVVM中V/C观察/订阅VM的变化,使用RAC这个“观察”工具。但是MVVM并不等于RAC,只是RAC是一种响应式的框架,它对于实现视图和模型之间的绑定和通信提供了一种非常好的解决方案,所以RAC只是实现这个"观察"的工具,RAC提供了优雅安全的数据绑定方式,是一个很好的工具。当然可以使用其他替代,比如可以通过KVO或定义接口回调的方法来完成这种双向通知的能力。
另外说一下,reactivecocoa的OC版其实已经停止维护很久了, 不管是换掉还是出了BUG自己读源码解决都是一个很大的工作,如果自己实现这个事件通知,又会增加很大的编码工作。
MVP和MVVM因为分层所以会建立MVC两倍以上的文件类,需要良好的代码管理方式。另外MVP和MVVM,V和P或者VM之间理论上是多对多的关系, 不同的布局在相同的逻辑下只需要替换V层, 而相同的布局不同的逻辑只需要替换P或者VM层. 但实际开发中P或者VM往往因为耦合了V层的展示逻辑退化成了一对一关系(比如SceneA中需要显示"xxx+Name", VM就将Name格式化为"xxx + Name". 某一天SceneB也用到这个模块, 所有的点击事件和页面展示都一样, 只是Name展示为"yyy + Name", 此时的VM因为耦合SceneA的展示逻辑, 就显得比较尴尬), 针对此类情况, 通常有两种办法, 一种是在VM层加状态进而判断输出状态, 一种是在VM层外再加一层FormatHelper. 前者可能因为状态过多显得代码难看, 后者虽然比较优雅且拓展性高, 但是过多的分层在数据还原时就略显笨拙, 大家应该按需选择.
有些文章上来就说MVVM是为了解决C层臃肿, MVC难以测试的问题, 其实并不是这样的. 按照架构演进顺序来看, C层臃肿大部分是没有拆分好MVC模块, 好好拆分就行了, 用不着MVVM. 而MVC难以测试也可以用MVP来解决, 只是MVP也并非完美, 在VP之间的数据交互太繁琐, 所以才引出了MVVM。
引用《杂谈: MVC/MVP/MVVM》,这篇文章对这三种模式诠释的很清楚,还有demo,大家可以详细去了解
看了大概的总序,可能不是那么理解,那下面我们来详细的,有浅入深的来分析。
2.MVC的前因后果
大概是在2010年左右移动端开发火了起来,起初是iOS,Android, WinPhone三个大平台竞争,后来后者退出了角逐,变成了二分天下。从应用体系结构以及为开发者提供的框架体系来看,两个平台都是推出了经典MVC三层结构的开发方式,这三层所代表的意义是模型层、视图层、控制层。这个开发框架的初衷其实也很简单:视图层负责展示和渲染,模型层负责业务逻辑的实现,控制层负责调度视图的事件以及业务逻辑的调用以及通知视图的刷新通知。三部分松散耦合,各司其职。下面是经典的MVC框架结构:
一个很可惜的事实是不管是Android和iOS都只对C和V两部分进行了标准的定义和实现:Android的视图部分的实现是定义了各种控件以及通过XML文件来组装视图布局界面,iOS的视图的实现也是定义了各种控件以及通过XIB或SB来组装视图布局界面;
Android的控制部分则是通过Activity来实现,而iOS的控制部分则是通过UIViewController来实现的。
而模型部分呢?因为每个应用的业务逻辑和应用场景并不相同,所以两个平台也无法也不能够定义出一个通用的模型层出来,而是把模型层的定义留给了开发者来实现。然而这为我们的开发者在使用MVC框架开发应用时埋下了隐患。
3.ios的MVC
上图描述的经典的MVC模式,M和View是完全隔离的,由C作为中间人来负责二者的交互(同时三者是完全独立分开的,这样可以保证M和V的可测试性和复用性,C都是为特别的应用场景下的M和V做中介者,所以很难复用)。但是实际上在iOS里面MVC的实现方式很难做到如上所述的那样,因为由于Apple的规范,一个界面的呈现都需要构建一个viewcontroller,而每个viewcontroller都带有一个根view,这就导致C和V紧密耦合在一起构成了iOS里面的C层,这明显违背了MVC的初衷。
apple里面的MVC真正写起来大概如下图所示:
这是massive controller的由来之一,没有区分V和C。那苹果为什么要这么干呢?苹果的解释简单来说,就是iOS里面的viewcontroller其实是view和controller的组合,目的就是为了提高开发效率,简化操作。完整的可以参考下apple对于MVC的解释。
apple搞出viewcontroller(VC)这么个玩意初衷可能是好的,写起来方便,提高开发效率嘛。确实应付简单页面没啥问题,但是一旦需要构建复杂界面,那么viewcontroller很容易就会出现代码膨胀,逻辑满天飞的问题,造成massive controller。而且容易把新手代入歧途,认为真正的MVC就是这么干的,导致很多新手都把本来view层的代码都堆到了VC,比如在VC里面构建view、view的显示逻辑,甚至在VC里面发起网络请求,所以一定要分清各层的职责。iOS 中各层的职责如下:
controller层(VC):
生成view,然后组装view
响应View的事件和作为view的代理
调用model的数据获取接口,拿到返回数据,处理加工,渲染到view显示
处理view的生命周期
处理界面之间的跳转
model层:
业务逻辑封装(一般都是和后台数据交互的逻辑,还有一些抽象的业务逻辑,比如格式化日期字符串为NSDateFormatter类型)
提供数据接口给controller使用
数据持久化存储和读取
作为数据模型存储数据
view层:
界面元素搭建,动画效果,数据展示,
接受用户操作并反馈视觉效果
4.massive controller现象
为什么人们经常会出现massive controller情况呢?根据上面的分析主要是两点
1)没有规范的model定义,开发者使用不当,把model的职责放在了VC上作。初学者经常把model当作简单的数据模型,只有简单的几个属性,把model的大量的工作放在了VC上,使VC越来越庞大。真正的model不仅有属性有方法,而且能解析数据存储数据,更能业务化数据模型,model是业务模型层是不是简单的数据模型。
2)V和C紧密耦合了,开发者把V的任务交给了VC,把VC当作C使用,而真正的C往往消失不见。因为苹果规范一个页面呈现建立一个VC,把V和C绑定在一起了。对于复杂的页面非常不适用。面对负责业务,应当只把VC当作一个业务场景,然后按照业务拆分成多个MVC,尽量对M甚至C隔离,尽量模块化,以达到复用性,易拓展易维护。
5.MVC的改造之瘦身
早期的应用开发相对简单,而且没有标准的模型层的定义,而控制层又在工程生成时留下了很多可供开发者写代码的地方,所以很多开发人员就自然而然的将业务逻辑、网络请求、数据库操作、报文拼装和解析等等全部代码都放入了控制层里面去了,根本就不需要什么模型层的定义。比如开始产品需求,展示学生列表,如下:
最小白做法就是,创建一个personListViewControl类,创建一个布局好三个label的sb/xib的cell view。其他的都放在personListViewControl,在personListViewControl里发送网络请求请求学生信息列表,解析请求到的json数据,然后加在xib显示。
好一点的做法是,单独建立一个只有属性的studentInfMode来解析的数据,专门建立一个NetAPI,封装请求学生信息列表的接口。cell view定义一个接口来接受要展示的数据。嗯,这样使personListViewControl大大瘦身。
但是想一些,数据解析在哪里呢,还是在personListViewControl,根据MVC的分工,数据解析应该放在model层,所以还需要在studentInfMode定义一个方法来解析数据。这样正在做到了C调用model的数据获取接口,拿到返回数据,处理加工,渲染到view显示。
至此M层工作全部移交到M层了,针对这样一个页面,看起来非常完美。但是有没有发现,显示的内容基本就是服务器返回的json,不需要加工。但是如果在要你实现这个页面呢?
如上图所示,cell的title,都是后面加上去的,服务器99%以上是不会返回的。所以这个时候呢?其实在model层增加VM,和业务相关的model。从服务器拿到json,在解析成studentdetailMode,在封住一个studentdetailViewMode,可以吧cell的height,headerTitle,HeaderHeight都放在这层处理。这样基本可以复用之前的View,也可以让VC瘦身。这种在model层封装ViewModel是仿照MVVM的做法,把某个界面和某个业务逻辑是绑定在一块的,但严格意义上框架还是MVC,因为通信机制还是仍然采用MVC的方式,在我看来它只是业务花了Model,当然对于M层的解释有很多,我认为这篇文章讲的非常到位,大家可以参考下:论MVVM伪框架结构和MVC中M的实现机。
上面的用法都是直接把VC当C用,只要充分利用好model,VC也不会太臃肿,但是随着应用的复杂增加,这种把VC当作C使用是完全不行的,如果现在产品需要加需求,如下所示,显示老师的信息。
这时候,我们在把VC当作C用,就不那么优雅合理了。这个还是简单的,如果还要其他的业务,比如查找功能。所以这个时候,我们应该把personListViewControl当作Scene。然后拆分成多个小MVC,模块化成教师模块和学生模块,通过personListViewControl(VC)拼接组装这两个子MVC来完成整个界面。
前后进化结构大致如下:
图上显示的并不是那么详细,实际做的时候还是会有其他很多小细节,所以用省略号表示。这样做,虽然比把修改前代码量要多,但是模块化比较清楚,方便后续修改,而且使personList瘦身不少。如果以后产品要修改,比如在其他地方也需要展示教师信息或者教师和学生信息拆开,放在tableBar上切换,修改后的结构改起来方便多了,但是修改前的改起来就很麻烦咯,基本需要重构啦。
至此MVC的改造基本完成了,当然实际应用比这复杂多了。大家想要深入,可以看看这里的demo,作者很用心,还写了demo来演示,引用《杂谈: MVC/MVP/MVVM》。
6.MVP
任何架构都不是完美的,MVC也一样。前面也说了,MVC的业务逻辑和业务展示强耦合,这对单元测试很不友好。而MVP刚好对这一缺点进行了优化,将业务逻辑和业务展示做了一层隔离,对应的就变成了MVCP。M和V功能不变,只是原来的C现在只负责布局,而所有的逻辑全部转移到了P层。
业务逻辑被转移到了P层, 以前的MVC,是C负责业务逻辑,C中直接处理V的的展示。那现在这功能在P层,P层此时的V层需要做哪些事情呢?只需要做两件事:
1.监听V层的数据更新通知, 刷新页面展示.(V是不知道有P的存在的,所以P要主动监听V,比如使用delegate,又比如使用block)
2.在点击事件触发时, 调用P层的对应方法, 并对方法执行结果进行展示.
7.MVVM
MVP其实上已经是一个很好的架构了,几乎解决了所有的问题那么为什么还会有MVVM呢?
MVP是需要P主动去监听的,如果V层有大量的数据更新需要监听,那么MVP就不那么友好了,这时候需要MVVM了,MVVM数据是双向绑定的,VM不需要主动去监听,V是知道VM的存在,会主动通知VM。而且在实现数据双向绑定的时候,我们还有一个很好的工具ARC,这样实现起来方便简洁很多,比MVP减少了大量的代码量。这就是MVVM和MVP的最大的区别了。
MVVM和MVP相对于MVC最大的改进在于:P或者VM创建了一个视图的抽象,将视图中的状态和行为抽离出来形成一个新的抽象。这可以把业务逻辑(P/VM)和业务展示(V)分离开单独测试,并且达到复用的目的,逻辑结构更加清晰
MVVM各层的职责 M层和MVC一样,负责业务逻辑层处理
view负责页面展示和渲染,但是view还多了数据的绑定,
VM对应MVP的P层,创建了一个视图的抽象,将视图中的状态和行为抽离出来形成一个新的抽象。这可以把业务逻辑(P/VM)和业务展示(V)分离开单独测试,并且达到复用的目的,逻辑结构更加清晰
好了,这只是自己学习相用学到的用自己的语言写下来,增强领悟。写的不对的地方多多指教。主要参考资料如下:
网友评论