微信号:Top100Case

介绍:TOP 100软件案例研究峰会是科技界一年一度案例研究盛会,每年甄选有代表的100个技术创新/研发管理案例,旨在揭幕100件案例背后的思考、长尾价值,为听众提炼最佳学习路径,帮助他人的项目或团队获得启示、成长,...

猿题库基于MVVM和MVC搭建架构的7点创新策略(附代码)

2017-05-26 08:35 蓝晨钰

导读  ID:TOP100case 

     导读:猿题库是一个拥有数千万用户的创业公司,从2013年题库项目起步到2015年,团队保持了极高的生产效率,产品完成了五个大版本和数十个小版本的高速迭代。在如此快速的开发过程中,如何保证代码的质量,降低后期维护的成本,以及为项目越来越快的版本迭代速度提供支持,成为了我们关注的重要问题。这篇文章将阐明猿题库iOS客户端的架构设计。


(全文共3833字   预计阅读时长:4分钟)

 MVC 


MVC,Model-View-Controller,我们从这个古老而经典的设计模式入手。采用 MVC 这个架构的最大的优点在于其概念简单,易于理解,几乎任何一个程序员都会有所了解,几乎每一所计算机院校都教过相关的知识。而在iOS客户端开发中,MVC作为官方推荐的主流架构,不但SDK已经为我们实现好了UIView、UIViewController 等相关的组件,更是有大量的文档和范例供我们参考学习,可以说是一种非常通用而成熟的架构设计。


但MVC也有它的坏处。由于MVC的概念过于简单朴素,已经越来越难以适应如今客户端的需求,大量的代码逻辑在MVC中并没有定义得很清楚究竟应该放在什么地方,导致它们很容易就会堆积在Controller里,成为了人们所说的 Massive View Controller。


 MVVM 


MVVM,Model-View-ViewModel,一个从MVC模式中进化而来的设计模式,最早于2005年被微软的WPF和Silverlight的架构师John Gossman提出。在iOS开发中实践MVVM的话,通常会把大量原来放在ViewController里的视图逻辑和数据逻辑移到ViewModel里,从而有效地减轻了ViewController的负担。


另外通过分离出来的ViewModel获得了更好的测试性,我们可以针对ViewModel来测试,解决了界面元素难于测试的问题。MVVM通常还会和一个强大的绑定机制一同工作,一旦ViewModel所对应的Model发生变化时,ViewModel的属性也会发生变化,而相对应的View也随即产生变化。


同样的,MVVM也有它的缺点:


一个首要的缺点是,MVVM的学习成本和开发成本都很高。MVVM是一个年轻的设计模式,大多数人对它的了解都不如MVC熟悉,基于绑定机制来进行编程需要一定的学习才能较好地上手。同时在iOS客户端开发中,并没有现成的绑定机制可以使用,要么使用KVO,要么引入类似ReactiveCocoa这样的第三方库,使得学习成本和开发成本进一步提高。


另一个缺点是,数据绑定使Debug变得更难了。数据绑定使程序异常会快速地传递到其他位置,在界面上发现的Bug有可能是由ViewModel造成的,也有可能是由Model层造成的,传递链越长,对Bug的定位就越困难。


同时还必须指出的是,在传统的MVVM架构中,ViewModel依然承载的大量的逻辑,包括业务逻辑,界面逻辑,数据存储和网络相关,使得ViewModel仍然有可能变得和MVC中ViewController一样臃肿。



 在两种架构中权衡而产生的架构 


两种架构的优点都想要,缺点又都想避开,我们在两种架构中权衡了它们的优缺点,设计出了一个新的架构,起了一个名字叫:MVVM without Binding with DataController,架构图如图1:


图1 MVVM without Binding with DataController架构图


ViewModel


先来看右边视图相关的部分,传统的MVC当中ViewController中有大量的数据展示和样式定制的逻辑,我们引入MVVM中ViewModel的概念,将这部分视图逻辑移到了ViewModel当中。在这个设计中,每一个View都会有一个对应的ViewModel,其包含了这个View数据展示和样式定制所需要的所有数据。同时,我们不引入双向绑定机制或者观察机制,而是通过传统的代理回调或是通知来将UI事件传递给外界。而ViewController只需要生成一个ViewModel并把这个装配给对应的View,并接受相应的UI事件即可。


这样做有几个好处:首先是View的完全解耦合,对于View来说,只需要确定好相应的ViewModel和UI事件的回调接口即可与Model层完全隔离;而ViewController可以避免与View的具体表现打交道,这部分职责被转交给了ViewModel,有效地减轻了ViewController的负担;同时我们弃用了传统绑定机制,使用了传统的易于理解的回调机制来传递UI事件,降低了学习成本,同时使得数据的流入和流出变得易于观察和控制,降低了维护与调适的成本。


DataController


接下来我们关注Model和VC之间的关系。如之前提到,在传统的MVVM中,ViewModel接管了ViewController的大部分职责,包括数据获取,处理,加工等等,导致其很有可能变得臃肿。我们将这部分逻辑抽离出来,引入一个新的部件,DataController。


ViewController可以向DataController请求获取或是操作数据,也可以将一些事件传递给DataController,这些事件可以是UI事件触发的。DataController在收到这些请求后,再向Model层获取或是更新数据,最后再将得到的数据加工成ViewController最终需要的数据返回。


这样做之后,使得数据相关的逻辑解耦合,数据的获取、修改、加工都放在Data Controller中处理,View Controller不关心数据如何获得,如何处理,Data Controller也不关心界面如何展示,如何交互。同时Data Controller因为完全和界面无关,所以可以有更好的测试性和复用性。


DataController层和Model层之间的界限并不是僵硬的,但需要保证每一个 ViewController都有一个对应的DataController。Data Controller更强调的是其作为业务逻辑对外的接口。而在DataController中调用底层的Model层逻辑是我们推荐的编程范式,例如数据加工层,网络层,持久层等。


在后面的例子中,我们会更详细地讲解DataController 的实现细节。


 Showme the code 


我们以猿题库主页(见图2)为例,展示我们是如何应用这个架构的。


图2  猿题库主页


主页由几个部分组成,最上面的小猴子Banner页,用于滚动展示一些活动信息;中间有一个用户名字的页面,用于展示用户信息和答题情况以及一些心灵鸡汤;底下的这部分是一个课目选择页面,展示了用户开启的科目入口,在更多选项里面可以进一步配置这些科目入口。接下来我们以科目页面(SubjectView)为例展示一些细节。


ViewController


我们会给每一个ViewController 都创建一个对应的DataController。例如我们给主页建一个类起名  APEHomePraticeViewController ,同时它会有一个对应的DataController起名叫  APEHomePraticeDataController  。同时我们把页面拆分为几个部分,每个部分有一个相对应的 SubView。代码如下:


  viewDidLoad  的时候,初始化好各个SubView,并设置好布局:



接下来,ViewController会向DataController请求Subject相关的数据,并在请求完成后,用获得的数据生成ViewModel,将其装配给SubjectView,完成界面渲染,代码如下:



数据结构


为了更好地演示,我们接下来要介绍一下Subject相关的数据结构:


APESubject  是科目的资源结构,包含了Subject的id和name等资源属性,这部分属性是与用户无关的;  APEUserSubject  是用户的科目信息,包含了用户是否打开某个学科的属性。



DataController


如我们之前所说,每一个ViewController都会有一个对应的DataController,这一类DataController的主要职责是处理这个页面上的所有数据相关的逻辑,我们称其为View Related Data Controller。



上面的这个代码定义如下。

  • 我们定义了一个界面最终需要的数据的property,这里是  openSubjects ,这个property会存储用户打开的科目列表,它的类型是  APESubject   。

  •  我们还会定义一个接口来请求openSubject数据。


DataController 这一层是一个灵活性很高的部件,一个DataController可以复用更小的DataController,这一类更小的DataController通常只会包含纯粹的或是更抽象的Model相关的逻辑,例如网络请求,数据库请求,或是数据加工等。我们称这一类DataController为Model Related Data Controller。


Model Related Data Controller 通常会为上层提供正交的数据:



在我们的  APEHomePraticeDataController  的实现中,就包含了一个  

   APESubjectDataController  ,这个    subjectDataController    会负责请求All Subjects和User Subjects,并将其加工成上层所最终需要的 Open Subjects。(备注:这个例子里面的callback会回调多次是猿题库产品的需求,如有需要,可在这一层控制请求都完成后再调用上层回调)


事实上,Model Related Data Controller可以一般性地认为就是大家经常在写的Model层代码,例如UserAgent,UserService,PostService之类的服务。之后读者若想重构项目就成这个架构,大可以不必纠结于形式,直接在DataController 里调用旧有代码的逻辑即可,如图3所示,这样的行为都是允许的。


图3  调用旧有代码进行重构


ViewModel


每一个View都会有一个对应的ViewModel,这个ViewModel会包含展示这个View所需要的所有数据。


我们可使用工厂方法来创建View Model,例如在这个例子里,Subject View Model 不需要关心传递给它是什么样的Subject,所有的科目或者只是用户开启的科目。



ViewModel可以包含更小的ViewModel,就像View可以有SubView一样。SubjectView 的内部是由一个   UICollectionView  实现的,所以我们也给了对应的Cell设计了一个 ViewModel。


需要额外注意的是,ViewModel 一般来说会包含显示界面所需要的所有元素,但粒度是可以控制的。一般来说,我们只把会因为业务变化而变化的部分设为ViewModel的一部分,例如这里的titleColor和backgroundColor会因为主题不同而变化,但其字号的大小(titleFont)却是不会变的,所以不需要事无巨细地都加到ViewModel 里。



View


View只需要定义好装配ViewModel的接口和定义好UI回调事件即可:



渲染界面的时候,完全依靠 ViewModel 进行,包括 View 的 SubView 也会使用 ViewModel 里面的子 ViewModel 渲染。



至此,我们就完成了所有的步骤。我们回过头再看一下ViewController的职责就会变得非常简单,装配好View,向DataController请求数据,装配ViewModel,配置给View,接收View的UI事,一切复杂的操作都能够代理出去。


 总结 


优点


通过上面的例子我们可以看到,这个架构有几个优点:


层次清晰,职责明确:和界面有关的逻辑完全划到ViewModel和View一边,其中ViewModel负责界面相关逻辑,View负责绘制;Data Controller负责页面相关的数据逻辑,而Model还是负责纯粹的数据层逻辑。ViewController仅仅只是充当简单的胶水作用。


耦合度低,测试性高:除开ViewController外,各个部件可以说是完全解耦合的,各个部分也是可以完全独立测试的。同一个功能,可以分别由不同的开发人员分别进行开发界面和逻辑,只需要确立好接口即可。


复用性高:解耦合带来的额外好处就是复用性高,例如同一个View,只需要多一个工厂方法生成ViewModel,就可以直接复用。数据逻辑代码不放在ViewController层也可以更方便地复用。


学习成本低:从本质上来说,这个架构属于对MVC的优化,主要在于解决Massive View Controller问题,把原本属于View Controller职责根据界面和逻辑部分相应地拆到ViewModel和DataController当中,所以是一个非常易于理解的架构设计,即使是新手也可以很快上手。


开发成本低:完全不需要引入任何第三方库就可以进行开发,也避免了因为MVVM维护成本高的问题。


实施性高,重构成本低:可以在MVC架构上逐步重构的架构,不需要整体重写,是一种和MVC兼容的设计。


缺点


不可否认的是,这个设计也有其相应的缺点,由于其把传统MVVM里面的VM拆成两部分,会造成下面的一些情况:


当页面的交互逻辑非常多时,需要频繁地在DC-VC-VM里来回传递信息,造成了大量胶水代码。


另外,由于在传统的MVVM中VM原本是一体的,一些复杂的交互本来可以在VM中直接完成测试,如今却需要同时使用DC和VM并附上一些胶水代码才能进行测试。


没有了Binding,代码写起来会更费劲一点(仁者见仁,智者见智)。


 后记 


MVVM是一个很棒的架构,私底下我也会用其来做一些个人项目,但在公司项目里,我会更慎重地考虑个中利弊。我做这个设计的时候,心仪MVVM的种种好处,又忌惮于它的种种坏处,再考虑到团队的开发和维护成本,所以最终设计成了如今这样。


个人认为,好的架构设计的都是和团队以及业务场景息息相关的。我们这套架构帮助我们解决了ViewController代码堆积的问题,也带来了更清晰明了的代码层级和模块职责,同时没有引入过多的复杂性。希望大家也能充分理解这套架构的适用场景,在自己的APP架构设计中有所借鉴。  


扩展阅读:案例榜单|1号店用户画像系统偏好算法和Storm优化实践

案例榜单|Hadoop生态系统在广告大数据技术的应用及选型

效率翻倍!百度外卖物流智能调度系统优化实践

架构升级:组件化可扩展平台架构迭代演进方式

缺人缺钱缺资源的小团队,如何搭建电商智能推荐机器学习系统

APP运行流畅到让大象跳舞!微软是怎么做性能优化的?

敏捷典范!1号店自动化发布系统部署流程(附流程图)

10万台服务器怎么实现自动化运维?腾讯梁定安解密织云系统!(附架构图)

完全云化的电商平台!美团云网络架构演进流程

让加载条消失!使用STF测试页面打开速度的方法

靠外包和借调搭建的新团队,效率却比成熟团队高2倍!

万字干货|淘宝性能自动化测试平台搭建过程

互联网下半场,产品创新要遵循的“望闻问切”之法

以银行系统为例,解析开放式云平台如何保障数据安全?

蚂蚁金服从0-1-100构建高效异地互联网研发团队的3大观点5大原则

征稿


寻找100个年度最具价值的实践案例

我们只要案例干货,拒绝广告


成为特约作者,你将:

◆ 连接100名年度经验与增长值TOP100的研发精英

◆ 提前入围「壹佰案例」年度最优案例榜单

◆ 案例整理成册,出版发行图书

◆ 成为msup客座教练

◆ 以观察员身份受邀出席TOP100全球软件案例研究峰会

◆ 所在公司享有msup活动优惠


有意者联系壹佰案例主编Cynthia

电话:18822091835

微信:EF0815

邮箱:fang.cheng@msup.com.cn

更多“壹佰案例”详情请移步官网查看↓↓↓


 
壹佰案例 更多文章 蚂蚁金服从0-1-100构建高效异地互联网研发团队的3大观点5大原则 以银行系统为例,解析开放式云平台如何保障数据安全? 互联网下半场,产品创新要遵循的“望闻问切”之法 万字干货|淘宝性能自动化测试平台搭建过程 靠外包和借调搭建的新团队,效率却比成熟团队高2倍!
猜您喜欢 30分钟构建视频FAQ tv metro应用 Uber技术栈全解析之上篇:基础 迎六一,DaoCloud带你去旧金山看鲸鱼 Java IO 的自述 HTTPS 的两三事