前言

前一阶段对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模式同样实现了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模式,解决了职责不清的问题。相对于MVP和MVVM而言,没有因回避职责划分问题导致的庞大而混乱的Presenter/Model。在仅仅是View样式不同的场景下,Model是可以复用的。而使用哪个View,可以通过重载Model的构造函数来决定。事实上,即使View的元素不同造成数据不同,Model也可以利用泛型等技术手段来达到重用代码的目的。

因为View并不依赖任何人,所以,我们可以很方便地把View替换成单元测试代码(View本身是可根据场景需要相互替换的),只要骗过Model就OK。这个测试类一旦被Model构造出来,就会自动验证数据、模拟用户更新数据和发出指令。

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

后记

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

我理解的MVC的更多相关文章

  1. 【blade的UI设计】理解前端MVC与分层思想

    前言 最近校招要来了,很多大三的同学一定按捺不住心中的焦躁,其中有期待也有彷徨,或许更多的是些许担忧,最近在开始疯狂的复习了吧 这里小钗有几点建议给各位: ① 不要看得太重,关心则乱,太紧张反而表现不 ...

  2. HttpModule的认识与深入理解及MVC运行机制

    转自:http://kb.cnblogs.com/page/50130/ ASP.NET MVC架构与实战系列之二:理解MVC路由配置 http://www.cnblogs.com/jyan/arch ...

  3. 理解Spring MVC Model Attribute和Session Attribute

    作为一名 Java Web 应用开发者,你已经快速学习了 request(HttpServletRequest)和 session(HttpSession)作用域.在设计和构建 Java Web 应用 ...

  4. 项目开发设计模式理解之MVC模式

    项目开发设计模式之MVC模式: M model 模型层 V view 视图层 C control 控制器 MVC模式在B/S架构下使用很广泛的软件设计模式,分成三个相对独立的模块构成,model+vi ...

  5. 深入理解Spring MVC(山东数漫江湖)

    初始工程 使用Spring Boot和web,thymeleaf的starter来设置初始工程.xml配置如下: <parent>   <groupId>org.springf ...

  6. 【译】理解Spring MVC Model Attribute 和 Session Attribute

    作为一名 Java Web 应用开发者,你已经快速学习了 request(HttpServletRequest)和 session(HttpSession)作用域.在设计和构建 Java Web 应用 ...

  7. 深入理解Spring MVC

    如何让一个普通类成为Controller? 方案一:实现接口Controller 解析:handleRequest(request,response) 方案二:继承AbstractController ...

  8. 深入理解Spring MVC 思想

    目录  一.前言二.spring mvc 核心类与接口三.spring mvc 核心流程图 四.spring mvc DispatcherServlet说明 五.spring mvc 父子上下文的说明 ...

  9. 转载:深入理解Spring MVC 思想

    原文作者:赵磊 原文地址:http://elf8848.iteye.com/blog/875830 目录  一.前言二.spring mvc 核心类与接口三.spring mvc 核心流程图 四.sp ...

随机推荐

  1. NPM (node package manager) 入门 - 基础使用

    什么是npm ? npm 是 nodejs 的包管理和分发工具.它可以让 javascript 开发者能够更加轻松的共享代码和共用代码片段,并且通过 npm 管理你分享的代码也很方便快捷和简单. 截至 ...

  2. 前端框架 EasyUI (0) 重新温习(序言)

    几年前,参与过一个项目.那算是一个小型的信息管理系统,BS 结构的,前端用的是基于 jQuery 的 EasyUI 框架. 我进 Team 的时候,项目已经进入开发阶段半个多月了.听说整个项目的框架是 ...

  3. ASP.NET Core 1.1.0 Release Notes

    ASP.NET Core 1.1.0 Release Notes We are pleased to announce the release of ASP.NET Core 1.1.0! Antif ...

  4. NET Core-学习笔记(三)

    这里将要和大家分享的是学习总结第三篇:首先感慨一下这周跟随netcore官网学习是遇到的一些问题: a.官网的英文版教程使用的部分nuget包和我当时安装的最新包版本不一致,所以没法按照教材上给出的列 ...

  5. 深入研究Visual studio 2017 RC新特性

    在[Xamarin+Prism开发详解三:Visual studio 2017 RC初体验]中分享了Visual studio 2017RC的大致情况,同时也发现大家对新的Visual Studio很 ...

  6. Python爬虫小白入门(四)PhatomJS+Selenium第一篇

    一.前言 在上一篇博文中,我们的爬虫面临着一个问题,在爬取Unsplash网站的时候,由于网站是下拉刷新,并没有分页.所以不能够通过页码获取页面的url来分别发送网络请求.我也尝试了其他方式,比如下拉 ...

  7. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  8. FFmpeg + SoundTouch实现音频的变调变速

    本文使用FFmpeg + SoundTouch实现将音频解码后,进行变调变速处理,并将处理后的结果保存为WAV文件. 主要有以下内容: 实现一个FFmpeg的工具类,保存多媒体文件所需的解码信息 将解 ...

  9. golang struct扩展函数参数命名警告

    今天在使用VSCode编写golang代码时,定义一个struct,扩展几个方法,如下: package storage import ( "fmt" "github.c ...

  10. Spring(三)__aop编程

    aop( aspect oriented programming ) 面向切面编程,是对所有对象或者是一类对象编程 几个重要的概念: 1.切面(aspect):要实现的交叉功能,是系统模块化的一个切面 ...