关于MVC、MVP、MVVM的一些看法

1,163 阅读11分钟

前言

关于MV(X)系列的架构模式的讨论从最初的MVC提出到现在的MVVM都没间断过(VIPER架构模式不做讨论),不同人对架构模式持有不同的观点,关于架构模式的博客也很多。从接触MVC开始,再到后面实际的运用,总会存在很多疑问,在面试中也被问倒了,面试官的话深深的触及我的灵魂,痛定思痛,专门花时间去深入了解这方面的知识。现在对于MV(X)系列的架构模式本人也有自己的看法,写这篇博客除与发表观点,同时算是一次总结。在补充下,文章更多的是从iOS开发角度出发去思考。

传统的MVC

MVC模式最早由Trygve Reenskaug在1978年提出,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件架构。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。——维基百科

MVC模式根据程序中的对象的职责或功能进行分类,分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC是一种复合模式,由多个基本的设计模式组合成。这也是MVC会被称为设计模式的原因。除了对程序中对象的分类,还规定了它们通信的方式。

V:负责UI展示和事件交互处理

M:数据存储和数据的处理

C:负责V和M的通信,管理V和M的生命周期。

来看下传统MVC的架构图(引用苹果的图):

引用苹果的图
从这个图中可以看到MVC是由策略模式,组合模式,观察者模式组合而成混合模式。View是以组合模式实现的,在iOS中把继承至UIResponder的类作为事件的响应者通过树形结构组合起来,用来表示部分以及整体层次。Controller是View的一个策略,View可以加载在不同的Controller上。View在Model上注册为观察者,Model数据变更时,View可以监听到Model变更的通知。

当View接收到用户事件时,将事件处理移交至Controller。Controller接收事件后可以请求Model进行数据变更或者请求View去更改行为或者外观。当Model的数据变更的时候,Model通知所有注册的观察者,View收到数据变更通知后,更新View外观。

在这种模式下,Controller是View的一个策略,同时还负责View的事件处理,还要负责通知Model更新数据。不同的View事件不同,不同的Model数据更新的接口也不同,在这种情况下,Controller是很难复用的。能复用的模块只有是View和Model。但从上图中可以看到View和Model存在耦合,View和Model没有完全隔离。View的更新依赖于Model。为了达到复用的目的,View的更新不应该依赖于Model,应该独立于Controller和Model,同理Model也是独立于Controller和View的。接下来我们看下苹果理想的MVC模式。

苹果理想的MVC

苹果理想的MVC,就是将View和Model完全隔离,View和Model之间不存在耦合,View和Model通过Controller这个中介者进行双方通信。在这里Controller是中介者模式的一种实现。View在这里也是命令模式的实现,通过Target-action机制,可以将View作为命令的执行者,对应Target。action就是一个的命令。响应链是调度者,负责给合适的执行者下发命令。

苹果理想的MVC架构图:

iOS中的MVC

在iOS里面我们知道有个UIViewController类,ViewController的出现源自苹果为了提高开发效率将View和Controller绑定到一起。从实际的使用上可以感觉到开发效率得到提高。对于新手快速入门也很方面,因为不需要去考虑任何架构模式,不用去严格的做不同对象的职责区分,大多数的初学者还是较习惯于大杂烩式的程序撰写,所以导致Massive Controller的出现。ViewController做的事就变得复杂,负责View和Model的生命周期,负责View和Model的通信。负责页面跳转,负责网络请求,负责处理数据等。这已经违反了设计模式的单一性原则。随着业务增多项目会变得难以维护。

iOS中的MVC架构图:

即便苹果如此设计,我们还是可以实现苹果理想的MVC。我们需要对ViewController进行瘦身。View的职责不变。业务逻辑抽离到Model中,ViewController中负责View和Model的生命周期,负责View和Model的通信,负责页面跳转。View将事件处理委托给ViewController,并对外提供更新UI的接口。ViewController,Model处理发起网络请求,处理数据完后通知ViewController。然后ViewController在去更新View。这样保持View和Model的隔离,达到易复用和易测试的效果,同时减轻了ViewController的负担。当业务增多的时候可以添加多个MVC对业务进行划分,避免讲业务代码都写在同一个MVC代码中。

MVC小结

MVC设计提出是为了实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能,MVC有助于应用程序长远的发展,可扩展性、可维护性和可测试性因为 MVC 的特性而变得容易,MVC对程序中的对象进行了划分,可以对View和Model进行复用。

但是MVC的缺点也很明显,传统的MVC中只有Model可以复用,View更新依赖于Model,View没有独立于Model和Controller,复用可能性小。Controller作为View和Model通信的通道,处理View提交的事件,同时负责通知Model更新数据等,Controller定制性过高本身就很难复用。从测试角度来说业务逻辑都放在Controller中就很难测试。在来说说苹果理想的MVC,这种模式下,Controller也是无法复用的,View和Model的复用性高。测试角度View和Model对外提供了接口,可以通过测试接口判断逻辑和界面更新是否正确。同时Controller需要手动实现View的更新的代码和各种事件代理通信方法。

MVP

我们先来看下标准的MVP架构图,是不是和理想的MVC架构图很像?Controller变成了Presenter,Presenter做了Controller的事,负责View和Model的通信,隔离View和Model使得它们之间解耦。MVC就变成了MVCP,Controller只负责管理View和Presenter的生命周期,通信的事就交由P来处理。MVP要解决的问题和理想的MVC解决的问题是一样的,只是表现形式有所差别。所以说MVP是MVC的变种,本质上还是MVC,只是引进Presenter对现有的MVC的功能进一步的划分。MVP也有很多分支,总体来说有两种形式Passive View和Supervising Controller。

Passive View

被动视图,在这种模式下视图层是被动的,和理想的MVC模式的View相同都是被动的。View对外提供更新界面的接口,Presenter可以通过接口对View进行更新。Presenter需要对外暴露和Model通信的接口。View通过Presenter的接口和Model通信。

在具体的实现中Presenter会持有View的弱引用,不负责管理View生命周期,当Model通知Presenter数据发生变更后,Presenter就可以更新View。为了达到结构的目的,让Presenter不依赖于View,通常结合业务界面定制一个View的协议,抽象出界面更新的方法,然后Presenter持有这个协议的引用,就不用依赖于具体的View。这点是符合设计模式的依赖倒置原则的。同时也可以结合业务抽象出Presenter的行为协议,可以切换不同的Presenter实现。这里的View是一种策略,Presenter也是一种策略,涉及的是策略设计模式。

Supervising Controller

监督控制器,从架构图可以看到View和Model是存在依赖的,可以相互通信。跟传统的MVC很像。这种模式下的View直接和Model交互,通过声明的方式绑定简单的UI更新逻辑需要的数据,不需要进过Presenter被动的更新界面。Supervising通知Model进行数据更新,Supervising只负责操作复杂UI逻辑更新,例如更改控件颜色,隐藏,显示等。

MVP小结

使用Passive View或Supervising Controller的决定主要取决于你希望应用程序的可测试性。如果需要可测试性高,使用Passive View是最合适的,可以通过测试Presenter测试所有的UI逻辑。如果希望代码简洁而不完全可测试,选择SuperVising,可以减少编写Presenter中UI更新的代码。

MVVM

在MVVM之前得先说下MVVM的发展历史,MVVM是马丁·福勒的PM(Presentation Model)架构模式的变体。MVVM和PM都来自MVC模式。MVVM由微软架构师Ken Cooper和Ted Peters开发,通过利用WPF(微软.NET图形系统)和Silverlight(WPF的互联网应用派生品)的特性来简化用户界面的事件驱动程序设计。MVVM也被称为model-view-binder。

Presentation Model

PM 模式与 MVP 比较相似,它从视图层中分离了行为和状态,PM以不依赖于特定用户界面平台的方式抽象出视图(创建了视图模型),MVVM以相同的方式抽象出视图的状态和行为。抽象出视图和我们上面提到的以协议的方式定义个视图模型是一个道理。和Presenter不同的是PM 模式将视图中的全部状态和行为放到一个单独的展示模型中(PM),协调Model并且为视图层提供一个接口。换句说PM 通过引入展示模型将模型层中的数据与复杂的业务逻辑封装成属性与简单的数据同时暴露给视图,让视图和展示模型中的属性进行同步。

MVVM 与 WPF

这里的VM也是PM,叫做视图模型。其他的没变。在 MVVM 的实现中,还引入了隐式的一个 Binder 层,而声明式的数据和命令的绑定在 MVVM 模式中就是通过它完成的。

MVVM旨在利用WPF中的数据绑定函数(使用隐式的 Binder 和 XAML 文件来完成视图和视图模型两者之间的双向绑定),从视图层中几乎删除所有GUI代码(代码隐藏),更好地促进视图层开发与模式其余部分的分离。

MVVM小结

MVVM模式通过Binder层对View和VM完成双向绑定减少手动更新View的代码编写,同时达到View和Model的分离的效果,让它们各司其职。在iOS中Binder层的实现可以通过RAC实现。同时MVVM存在缺点,MVVM的创造者表示实现MVVM的开销对于简单的UI操作是“过度的”,非常大的应用程序中的数据绑定会导致相当大的内存消耗。

总结

从MVC到MVVM,MV(X)系列的架构模式本质上都是从MVC上演化出来的,对程序的对象进行职责区分,减少View和Model之间的耦合提高它们的复用性,为了可测试性可以做进一步的优化。对于不同的架构模式的选择要根据适合开发平台的模式,苹果推荐使用MVC,安卓使用MVP,前端使用MVVM。当然可以根据项目需求考虑选择更适合的设计模式。无论选择什么架构模式,我觉得都要对这些架构模式有所了解,这样才能写出更好的项目。

参考文章

Model-View-Controller

MVC

浅谈 MVC、MVP 和 MVVM 架构模式

深入理解MVC

深入分析MVC、MVP、MVVM、VIPER

What are MVP-Passive View and MVP-Supervising controller

设计模式