MVVM之设计模式迷思

软件工程中伟大的MVC设计模式啊,让人既爱又恨。

关于MVC的种种,网上有很多。但是终归离不开

  • Model作为数据管理者
  • View作为数据展示者
  • Controller作为数据加工者

但同时Model和View又都是由Controller来根据业务需求调配,所以Controller还负担了一个数据流调配的功能。

所以三者的功能大致可以分为如下

M应该做的事:

  • 给ViewController提供数据
  • 给ViewController存储数据提供接口
  • 提供经过抽象的业务基本组件,供Controller调度

C应该做的事:

  • 管理View Container的生命周期
  • 负责生成所有的View实例,并放入View Container
  • 监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。

V应该做的事:

  • 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
  • 界面元素表达

Controller瘦身

当Controller里面由于业务逻辑的不断添加会越来越臃肿,测试的话自然不必多说,是越精简越好,代码的阅读以及后期维护都会很麻烦。那么接下来需要面对的就是一个臃肿的Controller,对其进行瘦身。

常规的瘦身方法核心都是将数据的处理以及与业务逻辑无关的部分部分进行提取分割:将这部分单独提取出来形成一个Store类(MVCS),将这部分提取出来放在Model中(MVVM)。

MVVM

MVVM 是 MVC 模式的一种演进,它主要解决了Controller过于臃肿带来的不易维护和测试的问题。其中 ViewModel 的主要职责是处理简单的业务逻辑并提供 View 所需的数据,这样 Controller 就不用关心业务与数据处理,自然也就瘦了下来。

ViewModel只关心业务数据不关心 View,所以不会与 View 产生耦合,也就更方便进行单元测试。View是一个壳,它所呈现的内容都需要由 ViewModel 来提供,而 View 又不与 ViewModel 直接沟通,这时就需要 Controller 来做中间的协调者。Controller持有 View 和 ViewModel,当 Controller 初始化时,会让 ViewModel 去取数据,简单来说就是调用 ViewModel 的某个获取数据的方法。

瘦Model、胖Model

瘦Model只是专门用于表达数据,然后存储、数据处理都交给外面的来做。MVCS使用的前提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去做。所以对应到MVCS,它在一开始就是拆分的Controller。因为Controller做了数据存储的事情,就会变得非常庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另一个对象去做,这个对象就是Store。这么调整之后,整个结构也就变成了真正意义上的MVCS。

胖Model将数据的处理、存储、表达都自己做,Controller需要的数据直接或者经过少量的操作就可以展示在View上,因此而胖Model还是包含了部分弱业务逻辑,强业务变动的可能性要比弱业务大得多,弱业务相对稳定,所以弱业务塞进Model里面是没问题的。另一方面,弱业务重复出现的频率要大于强业务,对复用性的要求更高,如果这部分业务写在Controller,类似的代码会洒得到处都是,一旦弱业务有修改(弱业务修改频率低不代表就没有修改),这个事情就是一个灾难。如果塞到Model里面去,改一处很多地方就能跟着改,就能避免这场灾难。

举例说明

在进行代码编辑的时候,某一个功能界面会过多的涉及到不同类型数据的展示。如,用户信息界面有来自好友用户、非注册用户、注册非好友用户、好友请求用户的信息展示,不排除后期还会添加其他的数据展示,一个方法是可以在Controller中进行if-else,这样的做法朴实简单,但是里面的种种操作不是Controller所需要进行或者知道的,比如对不同来源数据的判断以及对数据的进一步加工进而展示。那么可以参考胖Model的思路将这部分不需要Controller进行的部分提取出来—FriendViewModel。

FriendViewModel的作用就是接受不同的Model数据进行加工并输出给Controller中的View进行展示,FriendViewModel.m类似这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@class FriendModel,SearchResultModel,ApplyModel;

@interface FriendViewModel : NSObject

@property (nonatomic ,strong ,readonly) NSString * name;

@property (nonatomic ,strong ,readonly) NSString * phone;

@property (nonatomic ,strong ,readonly) NSString * email;

@property (nonatomic ,strong ,readonly) NSMutableArray * groupData;
@end

@interface FriendViewModel (FriendModel)
/**
* 通过好友模型进行初始化
*/
- (id) initWithFriendModel:(FriendModel *)friendModel;
/**
* 更新viewModel
*/
- (void) modifyViewModelWithFriendModel;

@end

@interface FriendViewModel (ApplyModel)
/**
* 通过好友请求模型进行初始化
*/
- (id) initWithApplyModel:(ApplyModel *)applyModel;
@end

@interface FriendViewModel (SearchModel)
/**
* 通过搜索好友结果模型进行初始化
*/
- (id) initWithSearchResultModel:(SearchResultModel *)searchResultModel;
@end

当然在FriendViewModel胖到一定程度的时候可以使用继承将其进行分割,这里只是单纯的使用分类进行不同模块的划分。