MVC
MVC,是模型(Model)-视图(View)-控制器(Controller)的缩写,其特点是:M和V相互隔离,通过C连接。
MVC分工
- Model: 数据封装,并定义操作和处理该数据的逻辑和计算。为Controller提供数据调度接口。模型对象可以与其他模型对象有一个或多个关系,因此有时应用程序的模型层实际上是一个或多个对象图。
- View:用户界面,可以响应用户操作。显示来自应用程序模型对象的数据,并允许编辑这些数据。提供简洁的构造方法,确保View的初始化工作完全封闭在View中。
- 控制器Controller:视图对象与模型对象之间的中介,并管理其他对象的生命周期。具体上则是负责业务逻辑、事件响应、数据获取等工作,管理View Container,负责生成所有的View实例,并放入View Container。Controller不一定就是UIViewController,也可以是任意继承自NSObject的对象。
MVC通信
- 模型层和视图层相互隔离,通过控制器对象连接。这也是MVC的主要特点。
- 视图层中创建或修改数据的用户操作通过控制器对象进行通信,并导致模型对象的创建或更新。当模型对象更改(例如,通过网络连接接收到新数据)时,它会通知控制器对象,控制器对象更新相应的视图对象。
- 视图对象通过应用程序的控制器对象了解模型数据的更改,并将用户发起的更改(例如,通过控制器对象在文本字段中输入的文本)传递给应用程序的模型对象。
- 控制器对象解释视图对象中的用户操作,并将新的或更改的数据传递到模型层。当模型对象更改时,控制器对象将新模型数据传递给视图对象,以便它们可以显示它。
MVC总结
- 任务均摊---View和Model确实是分开的,但是View和Controller却是紧密耦合的
- 可测试性---由于糟糕的分散性,只能对Model进行测试
- 易用性---与其他几种模式相比最小的代码量。熟悉的人很多,因而即使对于经验不那么丰富的开发者来讲维护起来也较为容易。
- 臃肿的Controller---随着业务增长和数据的愈发复杂,Controller中的代码会
MVCS
苹果自身就采用的是这种架构思路,从名字也能看出,也是基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操作的角度上讲,它拆开的是Controller。
MVCS如何分工
这算是瘦Model的一种方案,瘦Model只是专门用于表达数据,然后存储、数据处理都交给外面的来做。MVCS使用的前提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去做。所以对应到MVCS,它在一开始就是拆分的Controller。因为Controller做了数据存储的事情,就会变得非常庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另一个对象去做,这个对象就是Store。这么调整之后,整个结构也就变成了真正意义上的MVCS。
分工总结:
视图(View):用户界面
控制器(Controller):业务逻辑及处理
模型(Model):数据存储
存储器(Store):数据处理逻辑
MVCS是基于瘦Model的一种架构思路,把原本Model要做的很多事情中的其中一部分关于数据存储的代码抽象成了Store,在一定程度上降低了Controller的压力。
关于胖Model和瘦Model
胖Model (Fat Model)
cell.obj = obj;
胖Model包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上。
胖Model做了这些弱业务之后,Controller就会降低一些复杂度,只需要关注强业务代码就行了。众所周知,强业务变动的可能性要比弱业务大得多,弱业务相对稳定,所以弱业务塞进Model里面是没问题的。另一方面,弱业务重复出现的频率要大于强业务,对复用性的要求更高,如果这部分业务写在Controller,类似的代码会洒得到处都是,一旦弱业务有修改(弱业务修改频率低不代表就没有修改),这个事情就是一个灾难。如果塞到Model里面去,改一处很多地方就能跟着改,就能避免这场灾难。
然而其缺点就在于,胖Model相对比较难移植,虽然只是包含弱业务,但好歹也是业务,迁移的时候很容易拔出萝卜带出泥。另外一点,MVC的架构思想更加倾向于Model是一个Layer,而不是一个Object,不应该把一个Layer应该做的事情交给一个Object去做。最后一点,软件是会成长的,FatModel很有可能随着软件的成长越来越Fat,最终难以维护。
瘦Model(Slim Model)
cell.textLabel.text = obj.name;
cell.sunTextLabel = obj.sno;
瘦Model只负责业务数据的表达,所有业务无论强弱一律扔到Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,然后配套各种helper类或方法来对弱业务做抽象,强业务依旧交给Controller。
由于瘦Model跟业务完全无关,它的数据可以交给任何一个能处理它数据的Helper或其他的对象,来完成业务。在代码迁移的时候独立性很强。另外,由于瘦Model只是数据表达,对它进行维护基本上是0成本,软件膨胀得再厉害,瘦Model也不会大到哪儿去。
缺点就在于,由于Model的操作会出现在各种地方,瘦Model在一定程度上违背了DRY(Don’t Repeat Yourself)的思想,Controller仍然不可避免在一定程度上出现代码膨胀。
MVP
MVC的缺点在于并没有区分业务逻辑和业务展示, 这对单元测试很不友好。 MVP针对以上缺点做了优化,它将业务逻辑和业务展示也做了一层隔离,对应的就变成了MVCP。M和V功能不变,原来的C现在只负责布局,而所有的逻辑全都转移到了P层。
MVP中的V在iOS中指的是ViewController和View。MVP将MVC的Controller进行拆分:视图数据逻辑处理部分为P,Controller剩余部分与View合并成V。V和P之间通过Protocol进行通信。
视图数据逻辑:与视图相关的数据处理。例如将
NSDate
转换成NSString
。
MVP实现了各模块的解藕,具有更好的可测试性。但是总体代码量比MVC大。另外,iOS MVC更适用于快速开发,即代码规模较小的项目。因此将简单的MVC的Demo改成MVP,反而会显得笨拙。
这看起来不正是苹果所提出的MVC方案吗?确实是的,这种模式的名字叫做MVC,但是,这就是说苹果的MVC实际上就是MVP了?不,并不是这样的。如果你仔细回忆一下,View是和Controller紧密耦合的,但是MVP的协调器Presenter并没有对ViewController的生命周期做任何改变,因此View可以很容易的被模拟出来。在Presenter中根本没有和布局有关的代码,但是它却负责更新View的数据和状态。
MVP如何分工
MVP是第一个如何协调整合三个实际上分离的层次的架构模式,既然我们不希望View涉及到Model,那么在显示的View Controller(其实就是View)中处理这种协调的逻辑就是不正确的,因此我们需要在其他地方来做这些事情,比如用户输入操作,数据请求,数据处理等等业务逻辑。
分工总结:
视图(View):用户界面
模型(Model):数据存储
展示器(Presenter):数据处理,业务逻辑。
View和Presenter之间是完全解耦的,他们通过接口来交互
View和Presenter是一对一关系,意味着一个Presenter只映射一个View,且他们之间是可以双向交互的。
MVP 总结
- 任务均摊–我们将最主要的任务划分到Presenter和Model,而View的功能较少(虽然上述例子中Model的任务也并不多)。
- 可测试性–非常好,由于一个功能简单的View层,所以测试大多数业务逻辑也变得简单
- 易用性–在我们上边不切实际的简单的例子中,代码量是MVC模式的2倍,但同时MVP的概念却非常清晰
MVVM
在MVP的基础上,将P改成与V双向绑定的VM就变成了MVVM。
绑定是一种响应式的通信方式。View拥有ViewModel并监听ViewModel中变量值得变化,当ViewModel某个值变化时,View对象会感知并作出相应展示。可以使用KVO和RAC实现。例如在Label中显示倒计时,是V绑定了包含定时器的VM。
双向绑定在MVVM中指的是V和VM之间相互绑定。例如TextField的text长度达到阈值,另一个Button改变背景颜色。这个过程中首先VM感知V中TextField的text属性长度变化,V感知VM中对应的状态属性。一旦V中TextField的text属性长度超出VM中的阈值,VM中的状态属性改变,触发V中Button的背景色发生改变。
MVVM
MVVM 是 MVC 模式的一种演进,它主要解决了 ViewController 过于臃肿带来的不易维护和测试的问题。其中 ViewModel 的主要职责是处理业务逻辑并提供 View 所需的数据,这样 VC 就不用关心业务,自然也就瘦了下来。ViewModel 只关心业务数据不关心 View,所以不会与 View 产生耦合,也就更方便进行单元测试。
View 是一个壳,它所呈现的内容都需要由 ViewModel 来提供,而 View 又不与 ViewModel 直接沟通,这时就需要 ViewController 来做中间的协调者。
ViewController 持有 View 和 ViewModel,当 VC 初始化时,会让 ViewModel 去取数据,简单来说就是调用 VM 的某个获取数据的方法。
严格来说MVVM其实是MVCVM。从图中可以得知,Controller夹在View和ViewModel之间做的其中一个主要事情就是将View和ViewModel进行绑定。在逻辑上,Controller知道应当展示哪个View,Controller也知道应当使用哪个ViewModel,然而View和ViewModel它们之间是互相不知道的,所以Controller就负责控制他们的绑定关系,所以叫Controller/控制器就是这个原因。
归根结底就是一句话:在MVC的基础上,把C拆出一个ViewModel专门负责数据处理的事情,就是MVVM。然后,为了让View和ViewModel之间能够有比较松散的绑定关系,于是我们使用ReactiveCocoa,因为苹果本身并没有提供一个比较适合这种情况的绑定方法。iOS领域里KVO,Notification,block,delegate和target-action都可以用来做数据通信,从而来实现绑定,但都不如ReactiveCocoa提供的RACSignal来的优雅,如果不用ReactiveCocoa,绑定关系可能就做不到那么松散那么好,但并不影响它还是MVVM。
MVVM如何分工
视图(View)层:用户界面,视图展示。包含UIView以及UIViewController,View层是可以持有ViewModel的。
ViewModel层:视图适配器。暴露属性与View元素显示内容或者元素状态一一对应。一般情况下ViewModel暴露的属性建议是readOnly的。还有一点,ViewModel层是可以持有Model的。
Model层:数据模型与持久化抽象模型。数据模型很好理解,就是从服务器拉回来的JSON数据。而持久化抽象模型暂时放在Model层,是因为MVVM诞生之初就没有对这块进行很细致的描述。按照经验,我们通常把数据库、文件操作封装成Model,并对外提供操作接口。(有些公司把数据存取操作单拎出来一层,称之为DataAdapter层,所以在业内会有很多MVVM的变种,但其本质上都是MVVM)。
Binder:MVVM的灵魂。可惜在MVVM这几个英文单词中并没有它的一席之地,它的最主要作用是在View和ViewModel之间做了双向数据绑定。如果MVVM没有Binder,那么它与MVC的差异不是很大。
因为View、ViewModel以及Model间的清晰的持有关系,所以在三个模块间的数据流转有了很好的控制。
MVVM模式特点
- MVVM将ViewController视作View
- 在View和Model之间没有紧密的联系,一般的与View是一对一的关系。
- ViewModel与View之间是双向交互的
- 使用 MVVM 最舒服的姿势是搭配现在已经比较成熟的 ReactiveCocoa
MVVM 总结
- 任务均摊 – 在例子中并不是很清晰,但是事实上,MVVM的View要比MVP中的View承担的责任多。因为前者通过ViewModel的设置绑定来更新状态,而后者只监听Presenter的事件但并不会对自己有什么更新。
- 可测试性 – ViewModel不知道关于View的任何事情,这允许我们可以轻易的测试ViewModel。同时View也可以被测试,但是由于属于UIKit的范畴,对他们的测试通常会被忽略。
- 易用性 – 在我们例子中的代码量和MVP的差不多,但是在实际开发中,我们必须把View中的事件指向Presenter并且手动的来更新View,如果使用绑定的话,MVVM代码量将会小的多。
选择
- 越复杂的框架耦合度越小,但是开发速度越慢,反之亦然。所以要根据具体项目需求,在不同阶段决定框架。
- 如果模式之间存在兼容性,可选择混合开发。
分层架构
如按照数据层、网络层、业务层、界面层分层架构
MVC、MVP、MVVM都属于界面层