MVC并不属于GOF的23个设计模式之列,但是它在GOF的书中作为一个重要的例子被提出来,并给予了很高的评价。一般的来讲,我们认为GOF的23个模式是一些中级的模式,在它下面还可以抽象出一些更为一般的低层的模式,在其上也可以通过组合来得到一些高级的模式。MVC就可以看作是一些模式进行组合之后的结果。 MVC定义:即Model-View-Controller,把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样一个应用被分成三个层,即模型层、视图层、控制层。MVC模式结构如下: 图1-1 MVC模式组件类型的关系和功能 模型(Model):封装的是数据源和所有基于对这些数据的操作。在一个组件中,Model往往表示组件的状态和操作状态的方法。
[java] view plaincopy
[java] view plaincopy
[java] view plaincopy
比如实现一个模型、两个视图和一个控制器的程序。当用户在图形化用户界面输入一个球体的半径,程序除显示该球体的体积与表面积外,还将图形化显示该球体。该程序的4个类之间的示意图如下:
图1-2 一个模型、两个视图和一个控制器的基本结构 其中Model类及View1类根本不需要改变。对于Controller中的SphereWindows类,只需要增加另一个视图,并与Model发生关联即可。其关键实现代码为: [java] view plaincopy
图1-3 输出结果 在上面的程序中,我们只能通过键盘输入球体半径,现在我们修改以上程序,利用鼠标放大、缩小右边的球体图形,左边的半径、体积、表面积值同时跟着改变。此时的MVC模式为一个模型、两个视图和两个控制器,其结构如下: 图1-3 一个模型、两个视图和两个控制器的基本结构 其中Sphere、TextView与GraphicsView类与前面完全一样。在主程序SphereWindows中,该类这时不是直接作为Controller,它控制Controller1与Controller2的新建。该程序的关键代码为:
[java] view plaincopy
从上面介绍可以看出,通过MVC模式实现与图形用户化界面相关的应用程序具有极其良好的可扩展性。 MVC模式基本实现过程为: 1. 顶端控制器(如Java中的main程序入口)要新建模型; 2. 控制器要新建一个或多个视图对象,并将它们与模型相关联; 3. 控制器改变模型的状态; 4. 当模型的状态改变时,模型将会自动刷新与之相关的视图。 Java Swing、Java EE、Struts框架等都是使用MVC模式的典范。 Swing号称是完全按照MVC的思路来进行设计的。在设计开始前,Swing的希望能够达到的目标就包括: 模型驱动(Model-Driven)的编程方式。 提供一套单一的API,但是能够支持多种视感look-and-feel),为用户提供不同的界面。 很自然的可以发现,使用MVC模式能够有助于实现上面的这两个目标。 严格的说,Swing中的MVC实际上是MVC的一个变体:M-VC。 Swing中只显示的定义了Model接口,而在一个UI对象中集成了视图和控制器的部分机制。View和Control比较松散的交叉组合在一起,而更多的控制逻辑是在事件监听者部分引入的。 但是,这并没有妨碍在Swing中体现MVC的精髓。事实上,在Swing的开发初期,Swing确实是按照标准的MVC模式来设计的,但是很快的问题就出现了:View和Controller实际上是紧密耦合的,很难作出一个能够适应不同View的一般化的Controller来,而且,一般也没有很大的必要。 在Swing中基本上每一个组件都会有对应的Model对象。但其并不是一一对应的,一个Model接口可以为多个Swing对向服务,例如:JProgressBar,JScrollBar,JSlider这三个组件使用的都是BoundedRangeModel接口。这种模型的共享更能够充分的体现MVC的内涵。除了Model接口外,为了实现多个视感间的自由切换,每个Swing组件还包含一个UI接口,也就是View-Controller,负责对组件的绘制和接受用户输入。 Model-View是Subject和Obverser的关系,因而,模型的改变必须要在UI对象中体现出来。Swing使用了JavaBeans的事件模型来实现这种通知机制。具体而言,有两种实现办法,一是仅仅通知事件监听者状态改变了,然后由事件监听者向模型提取必要的状态信息。这种机制对于事件频繁的组件很有效。另外的一种办法是模型向监听者发送包含了已改变的状态信息的通知给UI。这两种方法根据其优劣被分别是现在不同的组件中。比如在JScollBar中使用的是第一种方法,在JTable中使用的是第二种方法。而对Model而言,为了能够支持多个View,它并不知道具体的每一个View。它维护一个对其数据感兴趣的Obverser的列表,使得当数据改变的时候,能够通知到每一个Swing组件对象。 在J2EE中,Sun更是将MVC提升到了一个体系结构模式的高度,这儿的MVC的含义就更为广泛了。与Swing中不同的是,在这儿MVC的各个部件不再是单纯的类或者接口,而是应用程序的一个组成部分! 在J2EE Blueprint中,Sun推荐了一种基于MVC的J2EE程序的模式。对于企业级的分布式应用程序而言,它更需要支持多种形式的用户接口。比如,网上商店需要一个HTML的界面来同网上的客户打交道,WML的界面可以提供给无线用户,管理者可能需要传统的基于Swing的应用程序来进行管理,而对商业伙伴,基于XML的Web服务可能对他们更为方便。 MVC无疑是这样一个问题的有效的解决方法,通过从控制和显示逻辑分离出核心的数据存取功能,形成一个Model模块,能够让多种视图来共享这个Model。 在J2EE中有几个核心的技术,JSP,JavaBean,Servlet,EJB。这里SessionBean,EntityBean构成了J2EE构架的基石。JSP能够生成HTML,WML甚至XML,它对应于Web应用程序中的View部分。EJB作为数据库与应用程序的中介,提供了对数据的封装。一般EntityBean封装的是数据,SessionBean是封装的是对数据的操作。这两个部分合起来,对应于Web应用程序的Model部分。在技术上,JSP能够直接对EJB进行存取,但这并不是好办法,那样会混淆程序中的显示逻辑和控制逻辑,使得JSP的重用性能降低。这时候有两种解决方法,通过JavaBean或者Servlet作为中介的控制逻辑,对EJB所封装的数据进行存取。这时,JavaBean或者Servlet对应于Web引用程序中的Controller部分。两种类型的Controller各有其优缺点:JSP同Servlet的交互不容易规范化,使得交互的过程变得复杂,但是Servlet可以单独同用户交互,实际上JSP的运行时状态就是Servlet;而由于JavaBean的规范性,JSP同JavaBean的交互很容易,利用JavaBean的get/set方法,JSP不需要过多的语句就可以完成数据的存取,这能够让JSP最大限度的集中在其视图功能上,而且,在桌面应用程序中使用JavaBean也很容易,而用Servlet就相对麻烦许多。根据不同的问题背景,可以选取不同的Controller,有时候也可以两者混合使用,或者直接在Servlet中调用JavaBean。 J2EE中的MVC是一个大的框架,这时我们往往把它不再看作为设计模式,而是作为体系结构模式的一个应用了。 Struts框架只实现了MVC的View和Controller两个部分,Model部分需要开发者自己来实现,Struts提供了抽象类Action使开发者能将Model应用于Struts框架中。 MVC的优点: (1)最重要的是应该有多个视图对应一个模型的能力。在目前用户需求的快速变化下,可能有多种方式访问应用的要求。例如,订单模型可能有本系统的订单,也有网上订单,或者其他系统的订单,但对于订单的处理都是一样,也就是说订单的处理是一致的。按MVC设计模式,一个订单模型以及多个视图即可解决问题。这样减少了代码的复制,即减少了代码的维护量,一旦模型发生改变,也易于维护。 其次,由于模型返回的数据不带任何显示格式,因而这些模型也可直接应用于接口的使用。 (2)由于一个应用被分离为三层,因此有时改变其中的一层就能满足应用的改变。一个应用的业务流程或者业务规则的改变只需改动MVC的模型层。 (3)控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起完成不同的请求,因此,控制层可以说是包含了用户请求权限的概念。 (4)它还有利于软件工程化管理。由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化产生管理程序代码。 MVC的不足体现在以下几个方面: (1)增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。 (2)视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。 (3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。 (4) 目前,一般高级的界面工具或构造器不支持MVC模式。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。
|
|