浅谈MVC

MVC(Model View Controller)也被很多人称作Massive View Controller,其原因在于Controller要接纳很多既不属于View,也不属于Model的东西,导致其十分臃肿。而代码一旦变得臃肿,问题就接踵而至,比如难以测试,容易出Bug等等。那么怎样能使Controller不那么臃肿呢,下面就谈一点自己在实际开发中的体会。

View Model

在MVC框架中,Model里面的数据通常需要经过一些处理之后才会在View中展示,比如Model里面的String可能要经过一些格式转换等等操作之后,才可以交给View去展示。这些转换之后的数据不能放在View里面,因为这违背View本身的定义,同时这些数据也不属于Model,那么只能将他们放在Controller里了。

为了将这部分数据从Controller解耦,我们可以在Controller和Model之间添加一层,称之为View Model,如下图:

MVVM

View Model的概念是从MVVM里面来的,大家可以查看这篇文章做更多的了解。

我们在一个项目中使用了这种结构,项目的需求大致是这样子的:

  • 要在移动端实现一个文件浏览系统,文件的目录结构来自网络服务器,移动端本地要缓存访问过的目录;
  • 文件会有多种不同类型,可以按照不同的方式排序;
  • 需要记录用户的访问历史;用户有自己订阅的文件列表;
  • 可以搜索,包括本地和远程;
  • 其它一些业务逻辑。

在项目的架构中,Model负责对整个目录结构模型的管理,采用sqlite存储,并使用Core Data访问sqlite,Core Data的强大功能使我们方便的实现数据查询、更新、插入、删除等操作。除此之外,Model还要负责数据的一些加密操作,缓存更新策略等。

而View Model管理和维护当前路径和当前目录列表,其职责主要有:

  • 与Model交互,从Model中获得数据,处理并生成用于View展示的数据结构;View只能处理View Model层的数据结构,而不能直接接触Model层的数据结构。

  • 与网络层的交互。在要访问的目录没有缓存时,View Model需要与网络层交互,从服务器端获得数据,同时也要把数据传给Model,用于更新Model内部的数据结构。

  • 管理View的状态。View会有不同的状态,比如‘等待’、‘搜索’、‘离线’等,在‘等待’状态下,我们需要做些等待的动画,等等…这些状态会影响多个View的显示,将它们放在View Model里面可以防止View之间过多的耦合。值得一提的是,当状态非常多时,我们可以在View Model中采用状态机(State Machine),这将十分有助于你理清状态切换的逻辑。

在整个设计中,Controller只是起到了View和View Model之间的桥梁作用,以及对一些系统消息的处理,比如Rotation,Low Memory Warning,Enter Backgorund等,具有非常轻的量级。

使用View Model的另外一个好处是使Unit Test变的更加简单,因为我们只要测试View Model,就能知道现在程序是否对错。

Data Source & KVO

根据Apple的官方文档,在iOS的MVC框架中,Model和View之间不能有任何耦合。

但是在添加了View Model之后,我在View Model和View之间建立了连接:View通过data source协议访问View Model,而View Model通过KVO通知View去更新。我的理由是:View Model本质上扮演的就是从Controller里面分离出来的data source的角色。

不过这个时候View需要强引用data source,下图概括了他们之间的关系

mvcvm

这让我少写了一点代码,让Controller变的更加轻量级,到目前为止,在这个两万多行代码的模块中,这种结构还没有出现副作用。

不过这其中也稍稍带了一点个人偏好,如果你不想让View与除了Controller之外的任何模块建立连接,这也是完全可行的。让Controller负责将数据从View Model传递给View,使用Objective C的动态消息转发机制,可以很容易的做到这一点,而不需要手动的在Controller里添加data source协议的每个函数。

设计模式

合理的使用一些设计模式,能有效的提高代码复用率,使整个MVC框架具有更轻的量级。

例如我们的View在不同的Device上有不同的布局,并且不是通过AutoLayout能够解决的,比如在iPad上我们要把路径的Bar放在顶部,而在大屏幕的iPhone(如iPhone 6/6 plus)上则要放在底部以便于用户点击。我们使用组合模式来解决这类问题,将界面拆分成小的部件,整体的View则是对这些部件的组装。这使得这些部件能够重复使用。

说到底,软件设计是仁者见仁、智者见智的问题,设计模式不是让我们生搬硬套的教条,让代码保持低耦合、高内聚才是其目的所在。