MVC 已经成为客户端的主流编程框架,相信客户端工程师对它并不陌生,甚至在开发过程中,不通过思考都会自动使用 MVC 框架编程。但在工作过程中,发现许多小伙伴也只是使用 MVC,对于为什么这样使用并不清楚。本篇文章将由浅入深,一步一步解释为什么要使用 MVC,以及使用 MVC 所带来的好处。
刚开始工作时,我是这样快速完成任务的
在刚开始工作时,拿到一个任务,第一时间想到的是怎么将它快速实现,但从未想过怎么将它做好,代码从头到尾都在文件里,一气呵成,速度比别人要快一倍,然后坐在那里,静静的等待着下一个任务的到来,有时还会偷偷的鄙视一下比我做得慢的同事,心中窃喜。但随着项目慢慢变大,问题就来了,一个 Controller 里的代码有时候会变成上千行,甚至几千行,代码的可读性变得非常差,存在大量重复代码,项目的一个小变更经常无法定位代码。经常干的事情就是全局搜索,替换相同的代码,一不小心改错一个,半天的工作得从头开始。
当年代码是这样的(伪代码):
这是一段简短的代码示例,但确能看出很多的问题。API 未封装、无统一网络接口、数据没模型、无任何编程思想等。
头破血流后,意识到代码复用的重要性
项目渐渐变大,编码几乎无法继续下去,面对需求变更,除了头疼,还是头疼。都说人感到痛苦的时候就是在成长,说得没错,这是我的第一次成长。在完成工作任务的空闲时间里,我开始试着重构自己的代码。我知道我所面对的直接问题是重复代码太多,需要代码复用,但对于工作经验不足的我,首先想到复用方法是函数。把网络请求、数据解析、数据存储、逻辑处理、页面刷新等都封装成了函数,代码结构瞬间变得清晰起来,Controller 里面代码减少 20%,成就感满满的。
项目越来越复杂,功能也一直在增加。渐渐发现自己的开发速度又在慢下来,上司对我工作也开始表现出不满。我知道自己的这种复用机制已经不能应对项目的复杂度。虽然在一个 Controller 里代码是可以复用了,但不同 Controller 之间的复用都是通过拷贝的,重复代码还是很多。而且拷贝过去后还要读懂其他逻辑,代码耦合度非常高。更糟糕的是,一个网络请求如果要增加或者减少一个 Header 参数,又得全局搜索,将所有文件都更改一遍,可扩展性极差。
改进后的伪代码:
虽然改进后代码还是很糟糕,但是代码可读性有了提高,并且同样接口调用已经不需要写第二次。
原来还有代码职责这回事
无论你考虑问题多么认真,限于已有知识的限制,做的事情不可能百分百完美,问题还是会接踵而来。当项目越来越大,工程文件目录越来越复杂,编写代码时变得非常困惑,不知道代码到底应该写在哪里,也不知道别人是不是已经曾经实现过类似的功能,到哪里去引用。或者有时候改一个页面属性,到处改都不生效。后来才发现在某个角落里,你已经对这个属性进行了修改。
这就是代码职责不清晰所造成的。比如在 View 里面只负责页面创建和刷新、Model 里面只负责数据处理和逻辑处理、Controller 只负责接口调用,连接 View 和 Model,并管理它们的生命周期。如果在 Controller 里面写了创建页面的代码,那么就是破坏了类的单一职责。一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。
职责不清晰就会造成代码耦合度高,破坏代码的复用性,并且代码的维护成本也变高。每一个类有每一个类的职责,每一类类有每一类类的职责,在项目开发前就都规定好,在后面的多人合作开发中就有规可循,编写对应功能的代码知道写在对应目录上,类似功能代码可以去固定文件夹中寻找,更改页面属性是只需考虑页面内代码。做到轻松愉快编程。
最后,终于明白了封装和继承的好处
庆幸的是,我的烂代码在项目演进而蔓延中,但并没有酿成不可挽回之势。我及时意识到了问题的严重性。
随着工作经验的积累,学到的技术越来越多,封装和继承在这正好能解决代码重用度低和可扩展性差的问题。于是我开始第二次重构自己代码。我写了个网络请求的基类,所有网络请求在这里都有个唯一出口,相同类型的网络请求我将他封装在一个类中,并且每个网络请求都有接口。数据我给它建立对应的模型,并在模型中解析对应数据源,再建立一个类来负责这个模型数据的增删改查和更新,如果有数据缓存,再创建一个类来管理这个模型的数据存储。这样一来,在不同 Controller 中用到同样的数据请求或模型,只需引用对应的类,调用相应的接口就行,不在需要再去理解相关代码逻辑,或者看 API 文档,真正实现了代码的封装性和复用性。而且网络接口类,模型类都有对应基类,代码的可变更性和扩展性也得到了保障。
改进后伪代码:
摸爬滚打中和 MVC 第一次相识
第一次相识,并不是指第一次听说,MVC 一直贯穿编程过程中,只是第一次感觉对它有些理解。做完代码职责后,就已经大体上遵循了 MVC 编程框架思想。只是上面没有特别说明将视图剥离出来。
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一个框架模式。MVC 三部分组成,各司其职,结构清晰明朗。程序的业务逻辑、数据、界面显示完全分离,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
Model(模型)表示应用程序数据,逻辑处理,数据库记录列表。模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
View(视图)是应用程序中处理数据显示的部分,视图处于交互层,用于将数据展示给用户,并传达用户的操作指令。
Controller(控制器)是应用程序处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
如下图所示:MVC 中 M 与 V 两者不存在任何直接交互。存在直接交互的只有 M 和 C 之间与 C 和 V 之间。首先我们来看 M 与 C 的交互,如图所示,有一条绿色的箭头从 C 发起指向了 M,代表了 C 与 M 之间的交互应该首先从 Controller 发起,C 首先向 M 提出自己的需求,再由 M 作出响应。C 具有导入 M 头文件的 API,因此 C 可以知晓 M 的一切内容,如同图中的那一条白色的虚线。再来看看相对复杂的 C 与 V 之间的交互,这次我们仍然从绿色的箭头开始了解,箭头仍然由 C 发起,可见 C与 V 之间的交互依旧由 Controller 发起,但箭头起始端写了 outlet 一个词,意思是“输出口”,outlet可以看作是从 C 指向 V 的指针,它在 C 中被定义。outlet 给我们提供了很大的方便,它使我们在 C 的内部就可以轻松准确地向 V 施令。C 可以拥有很多的 outlet,可以不止一个,这也使它可以更高效的和V进行交流。
我们以一个页面展示网络数据为例,来说明 MVC 之间元素的通信。首先用户通过 View 发出指令要刷新页面数据,然后 View 把这个指令通过 outlet 或 delegate 传达给 Controller,Controller 向 Model 提出要给它数据,Model 完成数据组织后返回给 Controller,Controller 再通过 data source 刷新 View。到这里,一条完整的通信就完成了。
为什么要用 MVC
MVC 的低耦合性、高重用性、可维护性等优点显而易见,使得原本复杂的代码与界面的交互变得简单、清晰、明了。而且 MVC 不同的层各司其职,有利于多人协作开发项目。
在我经历的几个工作阶段中,第一阶段编码无任何编程思想,所有代码都混杂在一起,显得混乱不堪,可读性非常差。要找一段逻辑,经常无法定位。
第二阶段知道用函数来包装一些重复代码,使得代码结构变得清晰。如果说之前编程思想是一页白纸的话,那么这一过程我开始在这页白纸上画了一笔,收获了面向过程的思想。编程思路渐渐清晰起来。
第三阶段开始接触继承和封装这些面向对象的高级特性,真正解决了之前编程过程中所面对的代码可读性差、复用性低、可维护性难等问题。如果把编程思想比作画画,那么经过这一阶段我已经能在白纸上画出形状。
第四阶段主要学会了和人协作。
而 MVC 的出现就是为了解决我们在工作各阶段所面临的问题,学习 MVC 编程框架,可以让你不用经历我所走过的弯路。