百度首页 | 百度空间
 
查看文章
 
MVC的迷失?
2008年04月09日 星期三 下午 09:33
作者:老王

补充:
本文中,似乎我对Web MVC的理解有问题,最近这个老生常谈的话题把我折磨的够呛。这里我再理理思路:传统的MVC概念是应用在桌面软件之上的,V可以查询M,C可以改变M。一条判断MVC的很重要的原则是View是否能动态的感知Model的变化,这在桌面软件上是很简单的,只要使用观测者模式就很容易办到,但是到了Web环境下,因为HTTP是无状态的,我们无法让View去观察Model,所以传统的MVC概念在Web环境下必须做出调整,这里最著名的改进方案就算是Sun公司针对Java做的Model2方案了,在Model2中,V并不直接和Mode打交道。一切和Model的交互都由Controller处理,然后Controller会把需要的Model数据放到一个View能找到的地方,并选择一个View去渲染结果。

基于这些认知,所以本文的一些观点可能并不正确,但我并不打算删除这篇文章,就留下来让大家继续讨论吧。

================================

MVC似乎早已成为一个让人感觉审美疲劳的名词了。仅PHP社区里,各种各样宣称支持MVC的框架少说也有几十种,不幸的是这些框架大部分都没有正确的实现MVC模式。今天容我老王卖一次瓜,说说目前这些所谓的MVC框架里疑是存在的问题。

首先一个问题是VIew是否有查询Model的权力?

在目前大多数MVC框架实现中,不管是修改Model状态的操作,还是查询Model状态的操作,所有和Model相关的操作都是在Controller中进行的。VIew里如果需要Model数据,一般也是先在Controller里完成对Model的操作,然后把需要的数据set到View里,从程序员的角度看,Controller在MVC中是一个中间人的角色。但我觉得这是错误的。因为在这样的MVC实现里,作为中间人存在的Controller过于万能,而VIew则显得有些残疾。在我看来,MVC中,针对M的操作,V和C是平等的。唯一的区别在于当请求达到Controller的时候,如果是修改Model状态的操作,会在Controller中进行,然后根据结果再转发给合适的View,如果仅仅是查询Model状态的操作,则Controller只是简单的把请求转发给合适的View,由View去查询Model的状态。之所以要进行这样的区分是因为不同的View对Model查询的需求也是不尽相同的,比如说一个列表页在IE浏览器里每页要显示100条,而在Ipnone手机里每页只能显示10条,这样的情况下,如果Model的查询是通过Controller进行的,那么我们的Controller是查100条还是10条呢?你可能想为每个需求都单独做一个Controller的Action方法,但那会产生若干一次性的方法,无疑是丑陋的解决方案,亦或许是进行某些if/else的判断,同样是自取其辱。所以我们说,应该在View里查询Model,而不是Controller。

再一个问题是Controller处于表现层还是应用层?

关于分层,一般都倾向于表现层,应用层,领域层,持久层的划分方式,从上至下单向依赖,在PoEAA这样圣经级别的著作中,Controller一般是作为表现层模式来介绍的,但实际上很多时候Controller是一个应用层模式。先来看一个例子,用户注册的时候,如果表单提交成功,会向用户的电子邮件里发一封欢迎信。在这个例子里,我们的Controller会先调用Model的相关方法完成用户的注册,然后可以使用PhpMailer之类的东西发送相关的电子邮件。假设Controller处于表现层,那么如果我们加入一个新的表现层,则无疑要重新实现一套Controller,这里就必然会导致重复实现发送电子邮件的逻辑。换句话说,我们的Controller里时常掺杂着一些应用逻辑。所以此时的Controller实际是处于应用层的,而且,Controller中Action的划定通常是根据用例来实施的,它本身勾画了用例的自然轮廓,进而从一个侧面说明了Controller和应用层的联系。

还有一个问题是Controller是否有权利redirect?

很多MVC框架的Controller都有一个和redirect相关的方法,用以实现从一个Action跳转到另一个Action的功能。但我要说明的是,这本质上是错误的,因为跳转是一个View关注的行为,而不是Controller。前面我们已经说过了,Controller属于应用层,而非表现层,一旦你在Controller里实现redirect操作,就会把表现层固化到Html界面之上,设想如果你要给你的应用实现一套命令行界面怎么办?在命令行里可是没有跳转一说的。或者你要用Delphi实现一个客户端软件,通过SOAP调用你的Controller程序,同样,Delphi客户端软件也不知道如何解析跳转。所以说Controller里不要使用redirect。

===============================

通过讨论,可以看到,焦点主要是集中在V和C上面,这并不奇怪,因为MVC本身就未涉及M的实现,归根到底,目前的MVC框架犯的主要错误就是没有清晰的切分V和C的职责,所以说他们不是正确的“M_V_C”,更像是一个“M_VC”。

类别:Php | 添加到搜藏 | 浏览() | 评论 (22)
 
最近读者:
 
网友评论:
1
2008年04月10日 星期四 下午 07:44
收藏:)
 
2
2008年04月11日 星期五 上午 01:03
web上的MVC是削足适履的MVC,是Model 2,建议老王看看swing的MVC,如果单看理论,那么《企业应用架构模式》里面也讲到了MVC。

MVC主要解决了两个问题──模型和视图的分离、控制器和视图的分离。
前者的目的在于实现单一模型支撑多种展现,而不论多少展现,其实质内容始终一致。
后者实际上是行为和表现的分离,但是这个目的并不是非常重要,所以一般的UI方案并不是很严格的执行这一原则。

三者的依赖关系遵循单向依赖原则:
1.模型不依赖视图,也不依赖控制器
2.视图依赖模型(他是模型事件的消费者),同时一般也依赖控制器(他是控制器的直接客户)。
3.控制器不依赖视图,而是依赖模型。

MVC常常具有自相似的结构──如果角度再高一点,你会发现,你所说的MVC实际上是一个大的MVC的V或者M,反过来也类似,有时候V或者M本身也可以再拆解成一个小的MVC
 
3
2008年04月11日 星期五 上午 08:12
To 自由天堂:谢谢你的留言。不过你是赞同我文中的论述?还是反对呢?
 
4
2008年04月11日 星期五 上午 08:19
It's very great!
 
5
2008年04月11日 星期五 下午 06:43
VIew是否有查询Model的权力,我一直很纳闷这个问题,现在也不能完全肯定。老王说的“MVC中,针对M的操作,V和C是平等的。唯一的区别在于当请求达到Controller的时候,如果是修改Model状态的操作,会在Controller中进行...”,能不能贴点伪代码?

Controller是否有权利redirect?我认为应该有吧,控制器之所以能“控制”,应该有点强行的执行能力才行,比如登陆失败,如果要强制跳转到另一个Action,应该由控制器执行,但我不否认在View中跳转,不过View仅作转向另一个视图用,至于在Controller执行跳转能否解析的问题,我想可以有变通的做法。
 
6
2008年04月11日 星期五 下午 10:47
简单快速至上
 
7
2008年04月13日 星期日 上午 11:55
to 老王:
对你的观点,我同意1,不同意2,半同意3 :-)
在“Controller处于表现层还是应用层”的讨论中,你举了一个发邮件的例子,不过我认为相应的逻辑应当在model中──这里的model应该是一个很rich的领域层,其中就包含各种业务逻辑。
不考虑技术方面的可行性的话,在MVC的架构模式下,controller实际上关心的是管理view与用户交互的“业务”逻辑,这与领域模型中的业务逻辑并不是同一回事。由于在web应用中,我们所谓的controller通常是php/asp/servlet这样的服务端脚本,所以距离model较近,距离view较远,结果影响了controller与view的紧密结合,这也是model 2与真正的mvc有所区别的原因。
由于web的这个特殊性,所以对于你说的第3点,我认为是需要灵活变通的,原则上controller不应该进行切换窗口的操作,但是web可以把redirect看成是一条操作链上的不同步骤,所以我认为这是可以考虑的,我比较赞同的做法是controller负责一个页面组,在这组页面内可以考虑redirect,而跨页面组的时候不使用controller,这可以约束controller的知识边界。
至于被固化到html页面,这个我认为是over requirement了,新的device往往意味着不同的UI交互方式,沿用旧的交互设计本来就
 
8
2008年04月13日 星期日 上午 11:59
faint,字数限制很bt阿,补充说完:

至于被固化到html页面,这个我认为是over requirement了,新的device往往意味着不同的UI交互方式,沿用旧的交互设计本来就很勉强,不如重新开发。另一方面,controller和view如此紧密,而且controller的正确实现一般会很薄,新开发成本并不高。

BTW:我现在用的是ROR,所以rich model比较容易,但是以前用struts/spring/hibernate的时候就不太可行,其实有的时候采用什么架构和设计原则,还是与你采用的技术方案有关的。
 
9
2008年04月13日 星期日 下午 07:29
To trooman:针对每个问题,我都举了一个例子来说明为什么。例子本身就是伪代码了,如果有疑问,可以到QQ群里大家讨论。

To 自由天堂:关于我举的那个用户注册发邮件的例子。你说发邮件是一个Rich Model的职责,我不同意这样的观点,发邮件本身是一个很技术化的动作,而对于领域实体而言,应该尽可能的剥离技术味道才能达到更大的可复用性。你现在用的是RoR,我们就说RoR对这样需求的实现,它也是在Controller里通过observer来实现的。
 
10
2008年04月14日 星期一 上午 00:59
"发邮件本身是一个很技术化的动作,而对于领域实体而言,应该尽可能的剥离技术味道才能达到更大的可复用性",这句话没看懂,什么叫“很技术化”,为什么要模型的复用性通常是通过分离关注点来完成的,“剥离技术味道”和这个没有必然联系阿.

ROR中对于什么地方发邮件并没有倾向性的意见,我遇到的情况是,当模型被影响时,产生一个通知邮件,这当然是依赖模型的。
 
11
2008年04月14日 星期一 上午 00:59
"发邮件本身是一个很技术化的动作,而对于领域实体而言,应该尽可能的剥离技术味道才能达到更大的可复用性",这句话没看懂,什么叫“很技术化”,模型的复用性通常是通过分离关注点来完成的,“剥离技术味道”和这个没有必然联系阿.

ROR中对于什么地方发邮件并没有倾向性的意见,我遇到的情况是,当模型被影响时,产生一个通知邮件,这当然是依赖模型的。
 
12
2008年04月14日 星期一 上午 09:49
To 自由天堂:我之所以说领域对象只有剥离了技术味道才能获得更大的复用性是因为逻辑也有领域逻辑和应用逻辑之分,不同的逻辑关注点不同,变化的频率也不同。领域逻辑是程序中最核心的逻辑,和问题领域息息相关,比如你把在取款机取款就是领域逻辑,因为取款是一个明确的领域概念,而当取款完成取款机吐出一个交易凭条就不是领域逻辑,而是应用逻辑,因为吐出交易凭条本身是一个很“技术化”味道的逻辑,设想一个VIP用户取款,可能就不是吐出凭条,而是更人性化的发送手机短信。这是因为应用逻辑有各种各样的可能性,所以我们不能把它固化在领域逻辑里。当然,你也可以在领域对象里if/else来实现需求,但那不过是另一个噩梦的开始。
 
13
2008年04月14日 星期一 上午 11:13
呵呵,讨论展开了。
我平时不太使用领域/应用这样的划分,因为这取决于你怎么定义你的问题域,所以有时会产生岐义(在不同的尺度上看是不同的模型)。
感觉取款机的例子不利于你的观点阿,因为具体做的时候你必须面对取款机本身的能力限制,它一般能够打印凭条,不过并不能够发送短信,不管你设计上怎么考虑,实现的时候只能采用后端系统发送消息队列的方式进行。
如果你有兴趣继续讨论,不妨举一个实践中的具体例子,这样我们都可以避免假设需求的错误。
 
14
2008年04月14日 星期一 下午 12:35
To 自由天堂:留言字数限制,没法详细阐述观点。我这几天抽空新写一篇博文,来继续讨论“领域逻辑和应用逻辑的问题”
 
15
2008年04月14日 星期一 下午 03:50
世事无绝对吧, 关于设计模式我无权发言, 但是我觉得程序的最重要的是结果而非过程, 被约束在条条框框里让人很头痛, 貌似有些争论永远都不会有结果.
 
16
2008年04月16日 星期三 下午 02:51
Controller不应该属于应用层,它与view结合太紧,主要处理输入输出。
至于发邮件之类的应用服务调用以及页面跳转的逻辑处理,应该有表现层
和应用层之间的Application Controller或页面流程Web Flow模式等来处理。
 
17
2008年05月03日 星期六 下午 06:51
我觉得老王可能混淆了mvc1与mvc2的区别
c到底是属于应用层还是表现层,没有绝对的界定。
 
19
2008年05月04日 星期日 上午 08:48
To 匿名网友:如我在补充中所写,我确实有些混淆。如果你有简单明了的解释,不妨说来听听。
 
20
2008年05月04日 星期日 下午 11:24
我对设计模式了解不多,看了老王几篇文章,我的理解:M是持久层,C在领域层和应用层都有,V自然是表现层。
最近在看一些php框架,也在尝试自己写,所以有一些比较感性的认识。
我基本上赞同老王的观点。正被一些框架里面Action和Model的操作搞得晕头转向,V和C是平等的概念豁然开朗。在php框架里面,基本上是把表现层理解为smarty那样的模板引擎,由C控制模板的输出,这本身就让C做了一部分表现工作。而V并没有封装好显示逻辑,尤其是事件机制。ROR没具体用过,但是知道它有RESTful的url,不知道是不是将显示逻辑封装好了。
php框架里面symfony做的还算不错,摒弃了模板引擎,直接用php做V,并且封装好了ajax操作。
 
21
2008年05月04日 星期日 下午 11:28
跳转的问题,我觉得是由于很多框架由C控制显示造成的,如果把显示逻辑完全放在V里面,就不会有Controller进行redirect的问题了。
 
22
2008年05月04日 星期日 下午 11:59
发邮件的问题,我觉得老王在observer的那篇文章可以说明问题了。
发邮件这样的逻辑应该独立出来,用observer的方式,在需要的地方随时绑定,而不是写死在controller或者model里面。
 
23
2008年05月06日 星期二 上午 09:13
http://www.industriallogic.com/xp/refactoring/catalog.html
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码:
 

     

©2008 Baidu