从MVC框架看MVC架构的设计(转)
尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是如何化解这些问题的,揭示其中所折射出的设计思想与设计理念。
MVC回顾
作为一种经典到不能再经典的架构模式,MVC的成功有其必然的道理,这个道理不同的人会有不同的解读,笔者最认同的一种观点是:通过把职责、性质相近的成分归结在一起,不相近的进行隔离,MVC将系统分解为模型、视图、控制器三部分,每一部分都相对独立,职责单一,在实现过程中可以专注于自身的核心逻辑。MVC是对系统复杂性的一种合理的梳理与切分,它的思想实质就是“关注点分离”。至于MVC三元素的职责划分与相互关系,这里不再赘述,下图给出了非常细致的说明。
图1:MVC组件的功能和关系[i]
View与Controller的解耦:mediator+二次事件委派
笔者早年开发基于swing的GUI应用时,在架构MVC的实践过程中深刻体会到了view与controller之间的紧密耦合问题。在很多事件驱动的GUI框架里,如swing,用户对view的任何操作都会触发一个事件,然后在listener的响应方法里进行处理。如果让view自己注册成为事件的listener,则必须要在view中加入对controller的引用,这不是MVC希望看到的,因为这样view和controller就形成了紧密的耦合关系。若将controller注册为listener,则事件响应将由controller承担,这又会导致controller处理其不该涉及的展现逻辑,造成view和controller难以解耦的原因在于:多数的用户请求都包含一定成分的展现逻辑和一定成分的业务逻辑,两种逻辑揉合在一个请求里,在处理的时候,view与controller很难合理地分工。解决这一问题的关键是要在view与controller之间建立一种可以将展现逻辑与业务逻辑进行有效分割的机制,在这方面,PureMVC[ii]的设计非常值得参考,它通过引入mediator+二次事件委派机制很好的解决了view与controller之间的紧耦合问题。
Mediator是一种设计模式,这种模式在组件化的图形界面框架中似乎有着普遍的应用场景,即使是在四人帮的《设计模式》一书中,也使用了一个图形界面程序的示例来讲解mediator。mediator的设计用意在于通过一个媒介对象,完成一组对象的交互,避免对象间相互引用,产生复杂的依赖关系。mediator应用于图形界面程序时,往往作为一组关系紧密的图形组件的交互媒介,完成组件间的协调工作(比如点选某一按钮,其他组件将不可用)。在PureMVC中,mediator被广泛应用,其定位也发生了微妙的变化,它不再只是图形组件间的媒介,同时也成为了图形组件与command之间的媒介,这使得它不再是可选的,而是成为了架构中的必需设施。对应到传统MVC架构中,mediator就是view与controller之间的媒介(当然,也依然是view之间的媒介),所有从view发出的用户请求都经过了mediator再传递给controller,它的出现在一定程度上缓解了view与controller的紧密耦合问题。
当view、mediator和controller三者被定义出来,并进行了清晰的职责划分后,剩下的问题就是如何将它们串联起来,以完成一个用户请求了,在这方面,事件机制起到了至关重要的作用。事件机制可以让当前对象专注于处理其职责范围内的事务,而不必关心超出部分由谁来处理以及怎样处理,当前对象只需要广播一个事件,就会有对此事件感兴趣的其他对象出来接手下一步的工作,当前对象与接手对象之间不存在直接依赖,甚至感知不到彼此的存在,这是事件机制被普遍认为是一种松耦合机制的重要原因。讲到这里插一句题外话,在领域驱动设计(Domain-Driven Design)里,著名的Domain Event模式也是有赖于事件机制的这一特性被创造出来的,其用意正是为了保证领域模型的纯净,避免领域模型对repository和service的直接依赖。回到PureMVC, 我们来看在处理用户请求的过程中,事件机制是如何串联view、mediator和controller的。在PureMVC里,当一个用户请求下达时,图形组件先在自身的事件响应方法中实现与自身相关的展现逻辑,然后收集数据,将数据置入一个新的event中,将其广播出去,这是第一次事件委派。这个event会被一个mediator监听到,如果处理该请求需要其他图形组件的协助,mediator会协调它们处理应由它们承担的展现逻辑,然后mediator再次发送一个event(这次的event在PureMVC里称之为notification),这个event会促使某个command执行,完成业务逻辑的计算,这是第二次事件委派。在两次事件委派中,第一次事件委派让当事图形组件完成“处理其职责范围内的展现逻辑”后,得以轻松“脱身”,免于被“协调其他图件处理剩余展现逻辑”和“选择并委派业务对象处理业务逻辑”所拖累。而“协调其他图形组件处理剩余展现逻辑”显然是mediator的职责,于是第一次广播的事件被委派给了mediator. mediator在完成图形组件的协调工作后,并不会插手“选择并委派业务对象处理业务逻辑”的工作,这不是它的职责,因此,第二次事件委派发生了,一个新的event由mediator广播出去,后被某个command响应到,由command完成了最后的工作——“选择并委派业务对象处理业务逻辑”。
图2:mediator+二次事件委派机制
总结起来,PureMVC是通过在view与controller之间引入mediator,让view与controller变成间接依赖,用户请求从view到mediator,再从mediator到controller均以事件方式委派,mediator+二次事件委派的组合可以说是一种“强力”的解耦机制,它实现了view与controller之间的完全解耦。
从Controller到Command,自然粒度的回归
目前,很多平台的主流MVC框架在设计上都引入了command模式,command模式的引入改变了传统MVC框架的结构,受冲击最大的就是controller。在过去传统的MVC架构里,一个controller可能有多个方法,每个方法往往对应一个user action,因此,一个 controller往往对应多个user action,而在基于command的MVC架构里,一个command往往只对应一个user action。传统MVC架构里将一个user action委派到某个controller的某个方法的过程,在基于command的MVC架构里变成了将useraction与command一一绑定的过程。如果说传统controller的管理方式是在user action与model之间建立“集中式”的映射,那么基于command的管理方式就是在user action与model之间建立“点对点式”的直连映射。
图3:从基于Controller到基于Command的架构演进
主流MVC框架向command转型是有原因的,除了command自身的优势之外,一个非常重要的原因就是:由于缺少合理的组织依据,controller的粒度很难拿捏。controller不同于view与model,view与model都有各自天然的粒度组织依据,view的组织粒度直接承袭用户界面设计,model的组织粒度则是依据某种分析设计思想(如OOA/D)进行领域建模的结果,controller需要同时协调view与model,但是view与model的组织结构和粒度都是不对等的,这就使得controller面临一个“在多大视图范围内沟通与协调多少领域对象”的问题,由于找不出合理的组织依据,设计者在设计controller时往往感到无所适从。相比之下,command则完全没有controller的困惑,因为command有一个天然的组织依据,这就是user action。针对一个user action设计一个command,然后将两者映射在一起,是一件非常自然而简单的事情。不过,需要说明的是这并不意味着所有command的粒度是一样的,因为不同的user action所代表的业务量是不同的,因此也决定了command是有“大”有“小”的。遵循良好的设计原则,对某些较“大”的command进行分解,从中抽离出一些可复用的部分封装成一些较“小”的command是值得推荐的。很多MVC框架就定义了一些相关的接口和抽象类用于支持基于组合模式的命令拼装。
不管是基于controller还是基于command,MVC架构中界定的“协调view与model交互”的控制器职责是不会变的,都需要相应的组件和机制去承载与实现。在基于command的架构里,command承担了过去controller的部分职责,从某种意义上说 command是一种细粒度的controller,但是command的特性是偏“被动”的。一方面,它对于view和model的控制力比controller弱化了很多, 比如,一般情况下command是不会直接操纵view的。另一方面,它不知道自己与什么样的user action映射在了一起,也不知道自己会在何种情况下被触发执行。支撑command的运行需要额外的注册、绑定和触发机制,是这些机制加上command一起实现了controller的职责。由于现在多数基于command的MVC框架都实现并封装了这些重要的机制,所以从某种意义上说,是这些框架自身扮演了controller角色。
小结
本文主要分析了过去传统MVC架构中存在的两大弊病:view与controller的紧密耦合以及controller粒度难以把控的问题,介绍了一些MVC框架是如何应对这些问题的,这些设计方案所体现出的优秀设计思想是非常值得学习的。
[i] 图片来源:http://java.sun.com/blueprints/patterns/MVC-detailed.html
[ii] PureMVC是一种MVC框架,最初使用ActionScript 3实现,现在在多种语言平台上有实现版本。官方站点:http://puremvc.org/
http://kb.cnblogs.com/page/502983/
从MVC框架看MVC架构的设计(转)的更多相关文章
- 从MVC框架看MVC架构的设计
尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点.本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是如何化解这些 ...
- java:(九大内置对象,计算服务器访问次数,filter过滤器,MVC框架,MVC和三层架构的关系)
1.九大内置对象: <%@ page language="java" import="java.util.*" pageEncoding="UT ...
- MVC框架与三层架构
MVC框架 介绍: MVC全名Model View Controller Model:模型的意思,代表业务模型 View:视图的意思,代表用户界面 Controller:控制器的意思,控制器接受用户的 ...
- ASP.NET MVC:看 MVC 源码,学习:如何将 Area 中的 Controller 放到独立的程序集?
背景 本文假设您已经熟悉了 ASP.NET MVC 的常规开发方式.执行模型和关键扩展点,这里主要说一下如何使用 ASP.NET MVC 的源代码解决一些问题. 如何将 Area 中的 Control ...
- MVC:开发模式&&三层架构:软件设计架构
MVC:开发模式 jsp演变历史 早期只有servlet,只能使用response输出标签数据,非常麻烦 后来又jsp,简化了Servlet的开发,如果过度使用jsp,在jsp中即写大量的java代码 ...
- 设计 REST 风格的 MVC 框架
http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/ 传统的 JavaEE MVC 框架如 Struts 等都是基于 Action 设计的后缀 ...
- mvc设计模式和mvc框架的区别
Spring中的新名称也太多了吧!IOC/DI/MVC/AOP/DAO/ORM... 对于刚刚接触spring的我来说确实晕了头!可是一但你完全掌握了一个概念,那么它就会死心塌地的为你服务了.这可比女 ...
- 使用Intellij Idea自定义MVC框架
---恢复内容开始--- 今天我学习了自定义一个简单的MVC框架,这个我们首先要知道什么是MVC框架! MVC框架: MVC全名是Model View Controller,是模型(model)-视图 ...
- 教你快速打造PHP MVC框架
简介 MVC框架在现在的开发中相当流行,不论你使用的是JAVA,C#,PHP或者IOS,你肯定都会选择一款框架.虽然不能保证100%的开发语言都会使用框架,但是在PHP社区当中拥有最多数量的MVC框架 ...
随机推荐
- 输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)
群里看到这道题,用python做了做, def find(array): v_sum = greatest = 0 for a in array: v_sum += a v_sum = 0 if v_ ...
- webservice使用基本技巧
一,webService基本概念 webService也叫XMLWeb SerVice WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独 ...
- C#读取Word文档内容代码
首先要添加引用com组件:然后引用: using Word = Microsoft.Office.Interop.Word; 获取内容: /// /// 读取 word文档 返回内容 /// //// ...
- display:box和flex的区别
没区别,仅是各阶段草案命名flex是最新的 但是在实际的浏览器测试中,display: flex 不能完全替代display: -webkit-box display: box 使用可以参考http: ...
- mysql 优化点小结
1.数据库表设计的合理性 1)三范式 一范式:原子性,属性不可分: 二范式:无部分依赖, 例:(学号, 课程名称) → (姓名, 年龄, 成绩, 学分),存在部分依赖 (学号) → (姓名, 年龄) ...
- 关于timestamp的二三事
之所以要写timestamp的随笔,是因为之前对它的理解存在误区,so. I have to remind myself by writing this informal essay. 微软文档链接: ...
- oracle系统包——dbms job用法(oracle定时任务)
用于安排和管理作业队列,通过使用作业,可以使ORACLE数据库定期执行特定的任务. 一.dbms_job涉及到的知识点1.创建job:variable jobno number;dbms_job.su ...
- expdp 备份数据库
操作系统层面创建目录 [root@Oracle10g ~]# mkdir -p /home/oracle/db_back/ 2. 修改目录的所属用户.所属组 [root@Oracle10g ~]# c ...
- 【原】AVAudio录制,播放 (解决真机播放音量太小)
原文链接:http://www.cnblogs.com/A--G/p/4624526.html 最近学习AVFoundation里的audio操作,最基本的录制和播放,参考了一个Code4pp的 一个 ...
- java_reflect_03
关于反射在annotation中的使用,这也是本次我个人学习反射的主要目的 关于什么是annotation后续我也会整理一下,现在只大致介绍一下 一,Annotation(注解)简介: 注解大家印象最 ...