我理解的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构造出来,就会自动验证数据、模拟用户更新数据和发出指令。

在项目中展开的话,结构如下图:

项目中展开的结构

后记

这不是结束,而是一个开始……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容