我理解的MVC
前言
前一阶段对MVC模式及其衍生模式做了一番比较深入的研究和实践,这篇文章也算是一个阶段性的回顾和总结。
经典MVC模式
经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。但对于Model和Controller的定义则较为模糊,以致在项目实践中对它们的职责产生了很多不同的理解。其中比较主流的有下面两种。
1、闭环党
比较传统,问题是Model和Controller职责不清,在实操时容易走形2、 开放派
MVP的前身,问题是Controller职责太重,优点是View和Model没有了直接的关联对MVP的一点浅见
如果我们希望View和Model脱离关联的话,那么很容易就会使得所有的职能都落到Controller头上,就如同上图所示。这样,Controller也就变成了Presenter,MVC也正式演化为MVP。所有的数据都由Presenter来驱动,所有的业务逻辑也由Presenter来实现。
MVP模式常见于Android,是谷歌官方推荐的App设计模式。从我找到的这张图上,可以非常明显地看出三者之间的关系。
Model只是一个数据通道,退化成了Repository如果将Model扩而大之,那么就会变成下面这张图描述的场景:
Model摇身一变,成了一个数据交换中心MVP模式的优点是实现了View和Model的解耦,缺点是Presenter职责太重。
对MVVM的一些理解
MVVM模式现在非常流行,下面这张图描述了MVVM模式各部分的关系和职能:
MVVM的内在关系和职能MVVM模式同样实现了View和Model的解耦。不过和MVP模式不同的是,MVVM是从数据驱动的角度出发来解决这个问题的。ViewModel和View实现双向数据绑定,ViewModel的数据变化自动地反应到View上。这样,ViewModel就代表了View,成了一个Agent。ViewModel也承担一点业务逻辑,但非常有限(也有把大量业务逻辑放在ViewModel里面的做法,这个就和MVP没什么本质区别了)。所以,业务逻辑的职能就只能由Model来扛了。事实上,MVVM模式本质上是MVC模式去掉了Controller或者说是Model合并了Controller。
MVVM模式的优点是双向数据绑定带来的便利,Model完全不需要关心View。以数据为核心的视角非常新颖,并且在处理业务逻辑上也更加直观。缺点其实和MVP一样,只不过它是Model臃肿而已。
我们为什么需要MVC?
MVC、MVP、MVVM(这里把它们统称为MVC),这种模式的真正好处是什么?说实话,这个问题好思考了很久,脑海里闪过的答案总觉得似乎还差那么一点。最终,梳理出以下几点,和大家探讨:
专业分工的需要
程序员和设计师的技能是完全不同的,用户界面就应该由设计师来完成而非程序员。所以,我们需要一个不包含任何逻辑代码的View,以便由设计师来完成用户界面的创建工作。程序员则可以专心去做和逻辑、数据相关的工作。在这个层面上,不需要考虑让人糟心的Model/Controller还是Model/Presenter或者Model/ViewModel如何划分职责的问题。View的分离显然非常成功!
应对变化的需要
既然用户界面的问题已经得到了完美解决,那么,就该轮到业务逻辑和数据处理的问题了。需求总是在不断变化,程序猿对于产品狗的敌意就来自于不断地更改需求。于是,少改、好改就成了程序员的永恒目标。行之有效的套路其实也就是分层解耦而已。无论是Model/Controller还是Model/Presenter或者Model/ViewModel,不过是分层的角度和方法不同而已。
流程工艺的需要
在软件工程领域,规范提得很多,流程提得很少,工艺几乎没有人提过。但在传统的制造业和工程施工领域,最核心的就是流程和工艺。流程和工艺,是工业化生产的组织和产品品质的基础。
在软件工程上,代码风格规范就是一种工艺,瀑布式开发或者敏捷开发都是流程。如何划分Model和Controller的职责,是软件的一个设计过程,也属于工艺的范畴。三者之间如何依赖,其本质是一个流程问题。被依赖的一定是先于依赖者被生产出来。明确了职责划分和依赖关系,才能科学地编制开发计划,保证产出的代码的品质和效率。
有『套路』可循,我们做起事情来总是会简单快捷许多。
传统MVC模式存在的问题
我们知道,经典MVC模式早先的主要问题是Model和Controller的职责不明,但现在,主要问题是无法进行单元测试。既然已经知道问题在哪里,那么,我们来想办法解决问题就好了,但这之前,还需要解决几个别的问题。
业务逻辑、界面逻辑和数据逻辑傻傻分不清
从广义上讲,无论是点击按钮打开一个对话框,还是拨动一个开关切换界面样式,或者验证输入数据的合法性,都是业务逻辑,并没有什么必要分得那么细。从某种意义上,把业务逻辑分为内在的、直接的反应,和需要用户进一步操作的,状态不确定的过程,可能更加有用。前者例如分页显示的列表用户点击了下一页按钮,从而产生了刷新数据的指令;后者例如用户点击了编辑按钮,是否会修改数据,修改成什么数据在这个时候都是未知的。
三者之间如何依赖
这是一个大问题!经典的VMC模式中,View是依赖于Model的。但我个人的理解是View是需求的最直接的体现,所以View应该是先验的存在,应该是Model依赖View而不是相反。在实际的项目中,在确定原型后,设计师和负责接口的程序员会同时开始工作。同时,测试工程师也会开始编写测试用例和单元测试代码。他们的工作依据都是产品给出的原型。
等一个View编写完成后,依赖于这个View的Model就可以开工了,每一个View都会对于着一个Model和若干的接口。最后,就轮到依赖于若干Model的Controller登场了。这样做的好处是整个开发过程是由底向上、由表入里的,整体过程非常自然,而且结构简洁明了。
如何划分Model和Controller的职责
这是一个更大的问题,足以引起开发者的争吵不休,就如同什么语言最好一样。
抛开这些分歧,我们就会看到,无论是什么模式,它所需要解决的无非是业务逻辑和数据问题!所以,我认为问题的本质不是谁负责什么,而是如何分离业务逻辑和数据。
特定的用户界面需要特点的数据,有着特定的业务逻辑。这个前提之下,解耦是没有意义的。我们要求的职责分明,无非是为了更容易应对变化。那么,都有哪些变化呢?
1. 界面样式变了,但业务逻辑和数据都没有变
2. 界面样式和业务逻辑变了,但数据没有变
3. 界面和数据变了,但业务逻辑没有变
4. 界面和数据没有变,但业务逻辑变了
5. 全都变了
界面的改变可以分为两种,一是样式改变,这种变化无关其他;另一种是元素变化,必然对应着数据的变化,需要修改接口。另外,就是业务逻辑的改变,而业务逻辑和数据并不存在必然关系。在MVC模式下,View的数据来源于Model,那么,Model的职责就是负责View和接口之间的数据交互,起一个数据通道或者说是数据引擎的作用。当数据发生变化的时候,只需要修改Model即可。
既然Model承担了数据引擎的职责,那业务逻辑就应该由Controller来承担。同时,因为不同的View之间也会存在交互,那么,也需要一共同的个中间人来进行转接和调度,由于Model和View是一对一的绑定关系,并不适合承担这个责任。所以,Controller负责业务逻辑是天然的。不过,那些和数据直接相关的事件,例如改变了一个选项后引起可用数据的变化、切换分页加载新数据之类的和具体业务没有关系的简单反应型的业务逻辑,我觉得交给Model去实现更简单和直接,并不一定要经过Controller去驱动Model。
在这种模式下,Model负责数据,Controller负责业务逻辑,整体是非常协调的。反过来看MVP和MVVM,因为回避职责的划分的问题导致了Presenter或Model的臃肿。
View和Model要不要双向数据绑定
非常有必要!传统的MVC是没有双向绑定的,这样,View上面数据的变化就必须通过Controller去修改Model。而建立双向绑定后,Controller就无需承担这个职责了,从整体上看,职责更加分明,逻辑也会更加简单。
改进的MVC模式
解决了以上四个问题,我们可以得到这样的一个新的MVC模式:
改进的MVC模式这种改进模式,相对传统的MVC模式,解决了职责不清的问题。相对于MVP和MVVM而言,没有因回避职责划分问题导致的庞大而混乱的Presenter/Model。在仅仅是View样式不同的场景下,Model是可以复用的。而使用哪个View,可以通过重载Model的构造函数来决定。事实上,即使View的元素不同造成数据不同,Model也可以利用泛型等技术手段来达到重用代码的目的。
因为View并不依赖任何人,所以,我们可以很方便地把View替换成单元测试代码(View本身是可根据场景需要相互替换的),只要骗过Model就OK。这个测试类一旦被Model构造出来,就会自动验证数据、模拟用户更新数据和发出指令。
在项目中展开的话,结构如下图:
项目中展开的结构后记
这不是结束,而是一个开始……