这是一篇大逆不道的文章,其作用就是供大家娱乐以及批斗,因为此文所提及的思想,试图改变现有的著名模式MVC的结构,因此如果认为MVC优秀甚至完美的话,还请直接忽略此文,以免影响心情。
本文将提出一种类似MVC但又不完全是现有的经典MVC的模式,该模式仅基于HTTP的Web系统中对经典的MVC模式进行改造,其特点是将View前置,通过View的切分来切分逻辑,形成多次M-V-C交互,最终生成响应。
经典MVC模式
对于经典的MVC模式,虽然从表面上看完全是个“不需要解释”的问题,但是每个人的理解又不尽相同。在我的理解中,MVC模式可以用下面这张图来表达:

基于HTTP的Web系统里,在经典的MVC模式中,一个请求的处理过程大致分为:
- Controller处理原始请求,根据请求的数据与系统的配置,寻找到真正处理该请求的逻辑,称之为Action。
- Action处理请求提交的数据,与Model进行交互,获取需要反馈的数据,并将这些数据按View的要求组装后交给View。
- View根据Action递交的数据,组装为用户可识别的视图模板,并通过响应传递到客户端进行渲染。
遭遇问题
这个模式一直工作得非常好,我也完全没有理由反驳他存在的意义和优势,直到有一天我发现我的页面有了些奇怪的需求:

页面也是一个经典的“个人日志”的页面,只是加入了一些SNS的元素,如右边有“最近来访”及“好友列表”模块,同时还有一个标签云。
对于此类系统,右边的模块往往会较为通用,但又随着页面内容的不同而有所变化。例如在“个人资料”页面,则不会有“最近来访”的模块,取而代之的是加入了“关注人物”模块,这种既有稳定性,又有动态性的模式无疑是设计的一大难关。
通常来说,如果我使用的是ASP.NET MVC框架,当出现这样的需求时,会创建一些Action Filter,用于集中各个模块的逻辑,随后将数据放入ViewBag这一属性中。得益于.NET 4.0提供的dynamic数据类型,可以在不定义Model基类的前提下将此类通用的数据传递给View。
随后只要在Action上声明需要哪些模块的数据即可:
[Friends]
[RecentVisitor]
[TagCloud]
public ViewResult ViewPost(int id) {
// 获取日志数据
}
从设计上而言,我认为这是一个合适的方式,通过AOP的方式抽取了一些公共的数据,交由View进行组装,符合传统的MVC模型,且不造成过多的冗余逻辑,直到有一天,我又遇到了一个问题:
由于系统希望引入异步模型,更好地利用多核及IO资源,并行地去获取各项数据,因此顺序执行的Action Filter不能再满足场景。
也就是说,我们希望通过将MVC中Action与Model的交互进行一些改造,将彼此没有强烈联系的各项数据的获取过程并行化:

这种方案是对Model组件的修改,Controller对应地进行配合,保持response对象直到所有数据以异步的方式获取完毕,一次性递交给View即可。这显然是一种可行且简便的实现,例如.NET 4.0的Parallel库就能很轻松地完成这一工作。但是在看到并应用这种手段时,我却不由得想到一个问题:
“好友列表”或者“近期访客”这样的模块,和日志本身有什么关系?
进一步的:
如果没有关系的话,为何一个Action需要处理这些数据(无论是通过Action Filter还是硬编码)?
在不断反思这个问题的同时,又会有这样的想法:
为什么一个响应只能由一个Action处理?明明不相关的几个区块,是不是由多个Action处理,形成多个View,再组装起来更合适呢?
现有方案
想到这一步,我就发现有1个常见的技术经常被用来处理此类页面,即“AJAX延迟加载”。在这种方案下,最初输出的View只包含对应模块的结构和容器,随后通过AJAX请求读取各模块的视图内容,由前端负责组装。
这种方案保证了页面的主要内容第一时间被送到客户端,也保证了各模块的逻辑(Action)互不重叠、冲突,实现干净利落的切分和隔离。但其代价是产生多个HTTP请求,在请求-响应级别的优化能力有限。
为了将HTTP请求数量进行缩减,此后又出现了BigPipe技术,虽然并没有改变经典MVC的结构,但做到了将“模块化页面”压缩至一个HTTP请求之中。同时BigPipe还能够更有效地利用浏览器的资源,让页面的渲染和内容的下载并行进行,对于复杂的、对浏览器渲染时间要求较高的页面有奇效。
综合分析了现有技术,却发现并没有理想的解答自己提出的问题。AJAX延迟加载虽然对Action进行了切分,却导致了HTTP请求过多等负面效果;而BigPipe并不是对逻辑处理的切分,只是一种HTTP响应输出技术,只能提供些许思路,却没有办法从根本上解决问题。
我的方案
于是又经过多天的反思,以及和一些朋友的讨论,最终我发现自己走进了一个“误区”:
传统MVC模型的请求由Controller开始,依次经过Action->Model->Action->View,最终变为输出。但这并不代表着此流程一定是“完美”的。
也许这个我认为的“误区”,对多数人来说是“真理”,因此要推翻这一点,确实得抱有足够的勇气……
首先,无论我们使用什么样的技术手段,对于用户来说,我们的页面是怎么样的:
- 主要内容为日志的标题和信息。
- 有一块显示最近来访的用户。
- 有一块显示作者的好友。
- 有一块显示标签云。
抛开底下的技术不谈,如果从视图和功能上进行划分,这显然是4个区块。但我们又是如何发现这是4个区块,而不是2个或者6个呢?答案是:在网页上用眼睛看。
是的,无论用什么样的技术,用户肉眼之所见,对他们来说永远是第一位。那么,为什么在逻辑上,却将供肉眼所见的“视图”放在最后呢?当然“因为一出来就给用户看了,当然要最靠近用户”这样的理由是成立的,但又为什么“用户一请求就应该收到了,因此要放在最前面”呢?
在这个近乎变态的想法的支撑下,我试图将View作为请求的入口,通过View的切割来产生不同的区块(Section),而不同的区块对应着不同的逻辑(Action< ->Model),又产生属于区块自己的视图,最终合并为整体:

在这一模式中,请求将有一个不同于经典MVC模式的处理过程:
-
请求被称为
Locator的组件接收,Locator组件会通过对请求数据的分析,定位到需要的视图。 - 视图定义整体页面的框架组织,以及各个区块(Section)。
-
视图引擎将解析View,当发现有Section时,进入处理Section的逻辑:
- 找到Section定义的对应的Action,并开始执行业务逻辑。
- Action与相关联的Model进行交互,取得数据。
- 将数据交付到该Action对应的View中。
- View输出Section对应的内容。
-
视图引擎将页面的框架组织,以及各个Section的输出内容,通过一个缓冲区(Buffer)进行合并,统一输出。
可见,这个另类的MVC模式有自己的一些特点:
- 请求最先从View开始,而不是Action。
- 一次请求会对应多个Action,而不是传统意义上的通过一次Action获取全部数据。
- View是分Section的,最后进行合并。
下一篇将会讲述此种模式的优势及应用场景,并简要地涉及相关的实现方案。
Pingback 引用通告: 另类MVC模式 – 优势及实现 | 宅居
基本上我就是这么干的。url决定最外层view是哪个,然后view来驱动数据查询,启动子view渲染。
嗯,其实我也是“瞎编”,这个模式早就想出来很久了,我只是想给他正个名,说明个思考这东西出来的思想过程。
我真正想做的是在这个模式的基础上,一统异步、BigPipe、AJAX等输出模式,这个在第2篇里说得比较多
是的,灵活
其实backbone在0.5版本把controller去掉改成router。
其实也和你这locator的想法异曲同工,弱化控制器强调路由。
前端和后台不一样,感觉大家深入实践一下,做一些复杂视图嵌套的业务系统之后,慢慢都会靠这个路子。
Yes,这篇就在这放着,哪天这个思路成了主流了,我就拿出去炫~~话说那个Controller,我一直称他为Dispatcher,和Locator或者Router都是一类名词,Dispatcher强调“分派”,分派的对象是能处理请求的完整流程的,因此接入的是Action;而Locator重在“定位”,定到就算数,别的不管,所以目标是View;Router就相对通用点,如果我存心做这框架,起名这事真得好好考虑
赞~
yahoo的很多广告产品采取的思路基本也是拆分开View然后分模块加载,实现方式是在php之抽象一个模块层,然后按View进行组合和读取,ER框架的组件其实也可以看作是一个小的view,如果给每个组件加上完整的数据获取和处理逻辑,再利用后加载实现按展示加载,然后一起渲染应该可以达到这个目的.只是随着模块的变化和增加,如何管理这些模块,这倒是个问题.
这么说吧,现有的MVC框架,上面加一层虚拟机,保管能达到这个目的,不过开发个虚拟机麻烦死……
ER的思路我是借鉴了,包括当初我设计的ER(见本博客的MVC这一标签)都带有类似的想法
从View开始的这个思路,我相信会有越来越多的应用,系统的复杂度正在逐渐靠向前端,因此View应当承担更多
其实在qeephp这个框架有个方案:把一些包含逻辑和显示的部分做成“控件”,控件拥有自已的业务逻辑和视图渲染能力,那么一个view里可能会使用到很多个控件,这些控件根据当前上下文参数,把每一块内容根据自身的业务逻辑显示出来。
最初的ASP.NET WebForm就有这功能,称为UserControl,而WebForm的生命周期模型是最适合干这事的
但是WebForm却被不少人责备,原因就是通过“控件”的模式来写,逻辑会过于分散
因此,我认为“控件”的方式,称其为“功能”。而在这基础上,再次收集各个“控件”里的逻辑,将他们集中在一起,变成Action,通过Controller统一调度Action,职责分明,“控件”由“Action+Model+View”组成,这个程度就称为“框架”,而这个时候,所谓的“控件”其实就是我说的“区块”了,名词的选择不同
哈哈,遇到知音了,很多年前webwork框架有这种思路的实现,跟你的想法一模一样,不过不搞java太多年了,不知道后继者Structs2还有没有保留这种方案
翻了下老书,再补充下,webwork里面称之为Page Controller,书上有张描述这个模式的图很好,可惜没法在这里发,另外好像rails3也开始有类似的支持了,我再去翻翻文档
原来就这么干的,就利用一个action然后分发给多个模块级action,现在应该很多框架都支持这么干