|
摘要:本文从视图、控制器、模型三个方面简要介绍了在Asp.net环境下,经典MVC设计模式的实现,并讨论了MVC设计模式的扩展,最后对MVC的优点及不足之处进行了分析。 关键词:设计模式、视图、控制器、模型 ASP.NET是微软最新推出的新型体系结构.NET框架的一部分,它为构造新一代动态网站和基于网络的分布式应用提供了强有力的支持。与以前的 Web 开发模型相比,ASP.NET 提供了许多重要的优点例如: 简易性;安全性;可管理性等。而且与基于过程的ASP页面技术相比,面向对象技术在ASP.NET中得到了完全实现。用传统ASP技术建立的Web应用实例中,在页面中同时实现显示,业务逻辑和流程控制,这从工程化的角度考虑,它有许多不足之处。用户界面承担着向用户显示问题模型和与用户进行操作和I/O交互的作用。用户希望保持交互操作界面的相对稳定,但更希望根据需要改变和调整显示的内容和形式。在.NET框架下ASP.NET技术结合MVC设计模式很好地解决了上述问题。 1 MVC设计模式简介 MVC由Trygve Reenskaug提出,首先被应用在SmallTalk-80环境中,是许多交互和界面系统的构成基础。MVC结构是为那些需要为同样的数据提供多个视图的应用程序而设计的,它很好的实现了数据层与表示层的分离。MVC作为一种开发模型,通常用于分布式应用系统的设计和分析中,以及用于确定系统各部分间的组织关系。对于界面设计可变性的需求,MVC(Model-View-Controller)把交互系统的组成分解成模型、视图、控制器三种部件。 视图部件把表示模型数据及逻辑关系和状态的信息以特定形式展示给用户。它从模型获得显示信息,对于相同的信息可以有多个不同的显示形式或视图。 控制器部件是处理用户与软件的交互操作的,其职责是控制提供模型中任何变化的传播,确保用户界面于模型间的对应联系;它接受用户的输入,将输入反馈给模型,进而实现对模型的计算控制,是使模型和视图协调工作的部件。 模型部件保存由视图显示,由控制器控制的数据;它封装了问题的核心数据、逻辑和功能的计算关系,它独立于具体的界面表达和I/O操作。 模型、视图与控制器的分离,使得一个模型可以具有多个显示视图。如果用户通过某个视图的控制器改变了模型的数据,所有其它依赖于这些数据的视图都应反映到这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知所有的视图,导致显示的更新。这实际上是一种模型的变化-传播机制。模型、视图、控制器三者之间的关系和各自的主要功能,如图1所示。 2 MVC设计模式的实现 ASP.NET提供了一个很好的实现这种经典设计模式的类似环境。开发者通过在ASPX页面中开发用户接口来实现视图;控制器的功能在逻辑功能代码(.cs)中实现;模型通常对应应用系统的业务部分。在ASP.NET中实现这种设计而提供的一个多层系统,较经典的ASP结构实现的系统来说有明显的优点。将用户显示(视图)从动作(控制器)中分离出来,提高了代码的重用性。将数据(模型)从对其操作的动作(控制器)分离出来可以让你设计一个与后台存储数据无关的系统。就MVC结构的本质而言,它是一种解决耦合系统问题的方法。 2.1 视图 视图是模型的表示,它提供用户交互界面。使用多个包含单显示页面的用户部件,复杂的Web页面可以展示来自多个数据源的内容,并且网页人员,美工能独自参与这些Web页面的开发和维护。 在ASP.NET下,视图的实现很简单。可以像开发WINDOWS界面一样直接在集成开发环境下通过拖动控件来完成页面开发本。本文中介绍每一个页面都采用复合视图的形式即:一个页面由多个子视图(用户部件)组成;子视图可以是最简单HTML 控件、服务器控件或多个控件嵌套构而成的Web自定义控件。页面都由模板定义,模板定义了页面的布局,用户部件的标签和数目,用户指定一个模板,平台根据这些信息自动创建页面。针对静态的模板内容,如页面上的站点导航,菜单,友好链接,这些使用缺省的模板内容配置;针对动态的模板内容(主要是业务内容),由于用户的请求不同,只能使用后期绑定,并且针对用户的不同,用户部件的显示内容进行过滤。使用由用户部件根据模板配置组成的组合页面,它增强了可重用性,并原型化了站点的布局。 视图部分大致处理流程如下:首先,页面模板定义了页面的布局;页面配置文件定义视图标签的具体内容(用户部件);然后,由页面布局策略类初始化并加载页面;每个用户部件根据它自己的配置进行初始化,加载校验器并设置参数,以及事件的委托等;用户提交后,通过了表示层的校验,用户部件把数据自动提交给业务实体即模型。 这一部分主要定义了WEB页面基类PageBase;页面布局策略类PageLayout,完成页面布局,用于加载用户部件到页面;用户部件基类UserControlBase即用户部件框架,用于动态加载检验部件,以及实现用户部件的个性化。为了实现WEB应用的灵活性,视图部分也用到了许多配置文件例如:置文件有模板配置、页面配置、路径配置、验证配置等。 2.2 控制器 为了能够控制和协调每个用户跨越多个请求的处理,控制机制应该以集中的方式进行管理。因此,为了达到集中管理的目的引入了控制器。应用程序的控制器集中从客户端接收请求(典型情况下是一个运行浏览器的用户),决定执行什么商业逻辑功能,然后将产生下一步用户界面的责任委派给一个适当的视图组件。 用控制器提供一个控制和处理请求的集中入口点,它负责接收、截取并处理用户请求;并将请求委托给分发者类,根据当前状态和业务操作的结果决定向客户呈现的视图。在这一部分主要定义了HttpReqDispatcher(分发者类)、HttpCapture(请求捕获者类)、Controller(控制器类)等,它们相互配合来完成控制器的功能。请求捕获者类捕获HTTP请求并转发给控制器类。控制器类是系统中处理所有请求的最初入口点。控制器完成一些必要的处理后把请求委托给分发者类;分发者类分发者负责视图的管理和导航,它管理将选择哪个视图提供给用户,并提供给分发资源控制。在这一部分分别采用了分发者、策略、工厂方法、适配器等设计模式。 为了使请求捕获者类自动捕获用户请求并进行处理,ASP.NET 提供低级别的请求/响应 API,使开发人员能够使用 .NET 框架类为传入的 HTTP 请求提供服务。为此,必须创作支持 System.Web.IHTTPHandler 接口和实现 ProcessRequest() 方法的类即:请求捕获者类,并在web.config 的 <httphandlers> 节中添加类。ASP.NET 收到的每个传入 HTTP 请求最终由实现 IHTTPHandler 的类的特定实例来处理。IHttpHandlerFactory 提供了处理 IHttpHandler 实例 URL 请求的实际解析的结构。HTTP 处理程序和工厂在 ASP.NET 配置中声明为 web.config 文件的一部分。ASP.NET 定义了一个 <httphandlers> 配置节,在其中可以添加和移除处理程序和工厂。子目录继承 HttpHandlerFactory 和 HttpHandler 的设置。 HTTP 处理程序和工厂是 ASP.NET 页框架的主体。工厂将每个请求分配给一个处理程序,后者处理该请求。 例如,在全局 machine.config 文件中,ASP.NET 将所有对 ASPx 文件的请求映射到 HttpCapture类: <httphandlers> ... <add verb="*" path="*.ASPx" type="Sys.UI.HttpCapture, Sys.UI"/> ... </httphandlers> 2.3 模型 MVC系统中的模型从概念上可以分为两类――系统的内部状态和改变系统状态的动作。模型是你所有的商业逻辑代码片段所在。本文为模型提供了业务实体对象和业务处理对象:所有的业务处理对象都是从ProcessBase类派生的子类。业务处理对象封装了具体的处理逻辑,调用业务逻辑模型,并且把响应提交到合适的视图组件以产生响应。业务实体对象可以通过定义属性描述客户端表单数据。所有业务实体对象都EntityBase派生子类对象,业务处理对象可以直接对它进行读写,而不再需要和request、response对象进行数据交互。通过业务实体对象实现了对视图和模型之间交互的支持。实现时把"做什么"(业务处理)和"如何做"(业务实体)分离。这样可以实现业务逻辑的重用。由于各个应用的具体业务是不同的,这里不再列举其具体代码实例。 3 MVC设计模式的扩展 通过在ASP.NET中的MVC模式编写的,具有极其良好的可扩展性。它可以轻松实现以下功能: ①实现一个模型的多个视图; ②采用多个控制器; ③当模型改变时,所有视图将自动刷新; ④所有的控制器将相互独立工作。 这就是MVC模式的好处,只需在以前的程序上稍作修改或增加新的类,即可轻松增加许多程序功能。以前开发的许多类可以重用,而程序结构根本不再需要改变,各类之间相互独立,便于团体开发,提高开发效率。下面讨论如何实现一个模型、两个视图和一个控制器的程序。其中模型类及视图类根本不需要改变,与前面的完全一样,这就是面向对象编程的好处。对于控制器中的类,只需要增加另一个视图,并与模型发生关联即可。该模式下视图、控制器、模型三者之间的示意图如图2所示。 图 2 视图、控制器、模型三者之间关系的示意图 同样也可以实现其它形式的MVC例如:一个模型、两个视图和两个控制器。从上面可以看出,通过MVC模式实现的应用程序具有极其良好的可扩展性,是ASP.NET面向对象编程的未来方向。 4 MVC设计模式的优点及不足之处 4.1 MVC的优点 MVC的优点体现在以下几个方面: (1) 可以为一个模型在运行时同时建立和使用多个视图。变化-传播机制可以确保所有相关的视图及时得到模型数据变化,从而使所有关联的视图和控制器做到行为同步。 (2) 视图与控制器的可接插性,允许更换视图和控制器对象,而且可以根据需求动态的打开或关闭、甚至在运行期间进行对象替换。 (3) 模型的可移植性。因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。需要做的只是在新平台上对视图和控制器进行新的修改。 (4) 潜在的框架结构。可以基于此模型建立应用程序框架,不仅仅是用在设计界面的设计中。 4.2 MVC的不足之处 MVC的不足体现在以下几个方面: (1)增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。 (2)视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。 (3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。 (4) 目前,一般高级的界面工具或构造器不支持MVC模式。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。 5 结束语 与软件所处理问题的内在模型相比较,用户界面是需要经常发生变化的,采用MVC设计模式可以在满足对界面要求的同时,使软件的计算模型独立于界面的构成。也可以基于此模型建立大型分布式应用程序框架。本文介绍了MVC设计模式的原理;MVC设计模式三个组成构件(模型部件、视图部件和控制部件)以及在ASP.NET环境下实现基于MVC的应用需要完成的工作;MVC设计模式的扩展;最后对MVC的优点及不足之处进行了分析。 MVC在我们项目中提供的这种对象和层之间的独立,将使我们的维护变得更简单使我们的代码重用变得很容易。 在我们的开发项目中使用MVC(Model-View-Control)模式的益处是,可以完全降低业务层和应用表示层的相互影响。此外,我们会有完全独立的对象来操作表示层。MVC在我们项目中提供的这种对象和层之间的独立,将使我们的维护变得更简单使我们的代码重用变得很容易(下面你将看到)。
作为一般的习惯,我们知道我们希望保持最低的对象间的依赖,这样变化能够很容易的得到满足,而且我们可以重复使用我们辛辛苦苦写的代码。为了达到这个目的我们将遵循一般的原则“对接口编成,而不是对类”来使用MVC模式。
我们的使命,如果我们选择接受它...
我们被委任构建一个ACME 2000 Sports Car项目,我们的任务是做一个简单的Windows画面来显示汽车的方向和速度,使终端用户能够改变方向,加速或是减速。当然将会有范围的扩展。
在ACME已经有了传言,如果我们的项目成功,我们最终还要为ACME 2 Pickup Truck 和ACME 1 Tricycle开发一个相似的接口。作为开发人员,我们也知道ACME管理团队最终将问“这样是很棒的,我们能够在我们的intranet上看到它?”所有的这些浮现在脑海中,我们想交付一个产品,使它能够容易的升级以便能够保证将来我们能够有饭吃。
所以,同时我们决定“这是使用MVC的一个绝好情形”
我们的构架概要
现在我们知道我们要使用MVC,我们需要指出它的本质。通过我们的试验得出MVC的三个部分:Model,Control和View。在我们的系统中,Model就是我们的汽车,View就是我们的画面,Control将这两个部分联系起来。
为了改变Model(我们的ACME 2000 sports car),我们需要使用Control。我们的Control将会产生给Model(我们的ACME 2000 sports car)的请求,和更新View,View就是我们的画面(UI)。
这看起来很简单,但是这里产生了第一个要解决的问题:当终端用户想做一个对ACME 2000 sports car一个改变将会发生什么,比如说加速或是转向?他们将通过View(our windows form)用Control来提出一个变化的申请。
现在我们就剩下一个未解决问题了。如果View没有必要的信息来显示Model的状态怎么办?我们需要再在我们的图中加入一个箭头:View将能申请Model的状态以便得到它要显示的相关状态信息。
最后,我们的最终用户(司机)将会和我们的ACME Vehicle Control系统通过View来交互。如果他们想发出一个改变系统的申请,比如提高一点加速度,申请将会从View开始发出由Control处理。
Control将会向Model申请改变并将必要的变化反映在View上。比如,如果一个蛮横的司机对ACME 2000 Sports Car做了一个"floor it"申请,而现在行驶的太快不能转向,那么Control将会拒绝这个申请并在View中通知,这样就防止了在交通拥挤是发生悲惨的连环相撞。
Model (the ACME 2000 Sports Car) 将通知View 它的速度已经提高,而View也将做适当的更新。
综上,这就是我们将构建的概要:
开始
作为总是想的远一点的开发人员,我们想让我们的系统有一个长久并且良好的生命周期。这就是说能够进可能的准备好满足ACME的很多变化。为了做到这一点,我们知道要遵循两条原则...“保证你的类低耦合”,要达到这个目标,还要“对接口编程”。
所以我们要做三个接口(正如你所猜测,一个Model接口,一个View接口,一个Control接口)。
经过很多调查研究,和与ACME人的费力咨询,我们得到了很多有关详细设计的信息。我们想确定我们可以设置的最大速度在前进,后退和转弯中。我们也需要能够加速,减速,左转和右转。我们的仪表盘必须显示当前的速度和方向。
实现所有这些需求是非常苛刻的,但是我们确信我们能够做到...
首先,我们考虑一下基本的项目。我们需要一些东西来表示方向和转动请求。我们做了两个枚举类型:AbsoluteDirection 和 RelativeDirection。 public enum AbsoluteDirection { North=0, East, South, West } public enum RelativeDirection { Right, Left, Back }
下面来解决Control接口。我们知道Control需要将请求传递给Model,这些请求包括:Accelerate, Decelerate, 和 Turn。我们建立一个IVehicleControl接口,并加入适当的方法。 public interface IVehicleControl { void Accelerate(int paramAmount); void Decelerate(int paramAmount); void Turn(RelativeDirection paramDirection); }
现在我们来整理Model接口。我们需要知道汽车的名字,速度,最大速度,最大倒退速度,最大转弯速度和方向。我们也需要加速,减速,转弯的函数。 public interface IVehicleModel { string Name{ get; set;} int Speed{ get; set;} int MaxSpeed{ get;} int MaxTurnSpeed{ get;} int MaxReverseSpeed { get;} AbsoluteDirection Direction{get; set;} void Turn(RelativeDirection paramDirection); void Accelerate(int paramAmount); void Decelerate(int paramAmount); }
最后,我们来整理View接口。我们知道View需要暴露出Control的一些机能,比如允许或禁止加速,减速和转弯申请。 public interface IVehicleView { void DisableAcceleration(); void EnableAcceleration(); void DisableDeceleration(); void EnableDeceleration(); void DisableTurning(); void EnableTurning(); }
现在我们需要做一些微调使我们的这些接口能够互相作用。首先,任何一个Control都需要知道它的View和Model,所以在我们的IvehicleControl接口中加入两个函数:"SetModel" 和"SetView": public interface IVehicleControl { void RequestAccelerate(int paramAmount); void RequestDecelerate(int paramAmount); void RequestTurn(RelativeDirection paramDirection); void SetModel(IVehicleModel paramAuto); void SetView(IVehicleView paramView); }
下一个部分比较巧妙。我们希望View知道Model中的变化。为了达到这个目的,我们使用观察者模式。
为了实施观察者模式,我们需要将下面的函数加入到Model(被View观察):AddObserver, RemoveObserver, 和 NotifyObservers。 public interface IVehicleModel { string Name{ get; set;} int Speed{ get; set;} int MaxSpeed{ get;} int MaxTurnSpeed{ get;} int MaxReverseSpeed { get;} AbsoluteDirection Direction{get; set;} void Turn(RelativeDirection paramDirection); void Accelerate(int paramAmount); void Decelerate(int paramAmount); void AddObserver(IVehicleView paramView); void RemoveObserver(IVehicleView paramView); void NotifyObservers(); }
...并且将下面的函数加入到View(被Model观察)中。这样做的目的是Model会有一个View的引用。当Model发生变化时,将会调用NotifyObservers()方法,传入一个对其自身的引用并调用Update()通知View这个变化。 public class IVehicleView { void DisableAcceleration(); void EnableAcceleration(); void DisableDeceleration(); void EnableDeceleration(); void DisableTurning(); void EnableTurning(); void Update(IVehicleModel paramModel); }
这样我们就将我们的接口联系起来了。在下面的代码中我们只需要引用我们这些接口,这样就保证了我们代码的低耦合。任何显示汽车状态的用户界面都需要实现IVehicleView,我们所有的ACME都需要实现IVehicleModel,并且我们需要为我们的ACME汽车制作Controls,这些Control将实现IVehicleControl接口。
下一步...在common中都需要什么
我们知道所有的汽车都做相同的动作,所以我们接下来做一个基于“骨架”的共有的代码来处理这些操作。这是一个抽象类,因为我们不希望任何人在“骨架”上开车(抽象类是不能被实例化的)。我们称其为Automobile。我们将用一个ArrayList (from System.Collections)来保持跟踪所有感兴趣的Views(记住观察者模式了吗?)。我们也可以用老式的数组来记录对IVehicleView的引用,但是现在我们已经很累了想快点结束这篇文章。如果你感兴趣,看一下在观察者模式中AddObserver, RemoveObserver, 和NotifyObservers,这些函数是怎样和IVehicleView互相作用的。任何时间当有速度或方向变化时,Automobile通知所有的IVehicleViews。 public abstract class Automobile: IVehicleModel { "Declarations "#region "Declarations " private ArrayList aList = new ArrayList(); private int mintSpeed = 0; private int mintMaxSpeed = 0; private int mintMaxTurnSpeed = 0; private int mintMaxReverseSpeed = 0; private AbsoluteDirection mDirection = AbsoluteDirection.North; private string mstrName = ""; #endregion "Constructor"#region "Constructor" public Automobile(int paramMaxSpeed, int paramMaxTurnSpeed, int paramMaxReverseSpeed, string paramName) { this.mintMaxSpeed = paramMaxSpeed; this.mintMaxTurnSpeed = paramMaxTurnSpeed; this.mintMaxReverseSpeed = paramMaxReverseSpeed; this.mstrName = paramName; } #endregion "IVehicleModel Members"#region "IVehicleModel Members" public void AddObserver(IVehicleView paramView) { aList.Add(paramView); } public void RemoveObserver(IVehicleView paramView) { aList.Remove(paramView); } public void NotifyObservers() { foreach(IVehicleView view in aList) { view.Update(this); } } public string Name { get { return this.mstrName; } set { this.mstrName = value; } } public int Speed { get { return this.mintSpeed; } } public int MaxSpeed { get { return this.mintMaxSpeed; } } public int MaxTurnSpeed { get { return this.mintMaxTurnSpeed; } } public int MaxReverseSpeed { get { return this.mintMaxReverseSpeed; } } public AbsoluteDirection Direction { get { return this.mDirection; } } public void Turn(RelativeDirection paramDirection) { AbsoluteDirection newDirection; switch(paramDirection) { case RelativeDirection.Right: newDirection = (AbsoluteDirection)((int)(this.mDirection + 1) %4); break; case RelativeDirection.Left: newDirection = (AbsoluteDirection)((int)(this.mDirection + 3) %4); break; case RelativeDirection.Back: newDirection = (AbsoluteDirection)((int)(this.mDirection + 2) %4); break; default: newDirection = AbsoluteDirection.North; break; } this.mDirection = newDirection; this.NotifyObservers(); } public void Accelerate(int paramAmount) { this.mintSpeed += paramAmount; if(mintSpeed >= this.mintMaxSpeed) mintSpeed = mintMaxSpeed; this.NotifyObservers(); } public void Decelerate(int paramAmount) { this.mintSpeed -= paramAmount; if(mintSpeed <= this.mintMaxReverseSpeed) mintSpeed = mintMaxReverseSpeed; this.NotifyObservers(); } #endregion }
现在我们的"ACME Framework"已经做好了,我们只需要设立有形的类和接口。首先让我们看看最后两个类:Control 和 Model...
这里我们有形的AutomobileControl实现IVehicleControl接口。我们的AutomobileControl也将设置View来依赖Model 的状态(当有向Model的申请时检测SetView方法)。
注意,我们只是有对IVehicleModel的引用(而不是抽象类Automobile )和对IVehicleView的引用(而不是具体的View),这样保证对象间的低耦合。 public class AutomobileControl: IVehicleControl { private IVehicleModel Model; private IVehicleView View; public AutomobileControl(IVehicleModel paramModel, IVehicleView paramView) { this.Model = paramModel; this.View = paramView; } public AutomobileControl() {} IVehicleControl Members#region IVehicleControl Members public void SetModel(IVehicleModel paramModel) { this.Model = paramModel; } public void SetView(IVehicleView paramView) { this.View = paramView; } public void RequestAccelerate(int paramAmount) { if(Model != null) { Model.Accelerate(paramAmount); if(View != null) SetView(); } } public void RequestDecelerate(int paramAmount) { if(Model != null) { Model.Decelerate(paramAmount); if(View != null) SetView(); } } public void RequestTurn(RelativeDirection paramDirection) { if(Model != null) { Model.Turn(paramDirection); if(View != null) SetView(); } } #endregion public void SetView() { if(Model.Speed >= Model.MaxSpeed) { View.DisableAcceleration(); View.EnableDeceleration(); } else if(Model.Speed <= Model.MaxReverseSpeed) { View.DisableDeceleration(); View.EnableAcceleration(); } else { View.EnableAcceleration(); View.EnableDeceleration(); } if(Model.Speed >= Model.MaxTurnSpeed) { View.DisableTurning(); } else { View.EnableTurning(); } } }
这里是我们的ACME200SportsCar类(从抽象类Automobile继承,实现了IVehicleModel接口): public class ACME2000SportsCar:Automobile { public ACME2000SportsCar(string paramName):base(250, 40, -20, paramName){} public ACME2000SportsCar(string paramName, int paramMaxSpeed, int paramMaxTurnSpeed, int paramMaxReverseSpeed): base(paramMaxSpeed, paramMaxTurnSpeed, paramMaxReverseSpeed, paramName){} }
现在轮到我们的View了...
现在终于开始建立我们MVC最后一个部分了...View!
我们要建立一个AutoView来实现IVehicleView接口。这个AutoView将会有对Control和Model接口的引用。 public class AutoView : System.Windows.Forms.UserControl, IVehicleView { private IVehicleControl Control = new ACME.AutomobileControl(); private IVehicleModel Model = new ACME.ACME2000SportsCar("Speedy"); }
我们也需要将所有的东西包装在UserControl的构造函数中。 public AutoView() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); WireUp(Control, Model); } public void WireUp(IVehicleControl paramControl, IVehicleModel paramModel) { // If we're switching Models, don't keep watching // the old one! if(Model != null) { Model.RemoveObserver(this); } Model = paramModel; Control = paramControl; Control.SetModel(Model); Control.SetView(this); Model.AddObserver(this); }
下面,加入我们的Button和一个label来显示ACME2000 Sports Car的状态还有状态条用来为所有的Buttons来显示编码。 private void btnAccelerate_Click(object sender, System.EventArgs e) { Control.RequestAccelerate(int.Parse(this.txtAmount.Text)); } private void btnDecelerate_Click(object sender, System.EventArgs e) { Control.RequestDecelerate(int.Parse(this.txtAmount.Text)); } private void btnLeft_Click(object sender, System.EventArgs e) { Control.RequestTurn(RelativeDirection.Left); } private void btnRight_Click(object sender, System.EventArgs e) { Control.RequestTurn(RelativeDirection.Right); }
加入一个方法来更新接口... public void UpdateInterface(IVehicleModel auto) { this.label1.Text = auto.Name + " heading " + auto.Direction.ToString() + " at speed: " + auto.Speed.ToString(); this.pBar.Value = (auto.Speed>0)? auto.Speed*100/auto.MaxSpeed : auto.Speed*100/auto.MaxReverseSpeed; }
最后我们实现IVehicleView接口的方法。 public void DisableAcceleration() { this.btnAccelerate.Enabled = false; } public void EnableAcceleration() { this.btnAccelerate.Enabled = true; } public void DisableDeceleration() { this.btnDecelerate.Enabled = false; } public void EnableDeceleration() { this.btnDecelerate.Enabled = true; } public void DisableTurning() { this.btnRight.Enabled = this.btnLeft.Enabled = false; } public void EnableTurning() { this.btnRight.Enabled = this.btnLeft.Enabled = true; } public void Update(IVehicleModel paramModel) { this.UpdateInterface(paramModel); }
我们终于结束了!!!
现在我们可以来测试ACME2000 Sports Car了。一切按计划进行,然后我们找到ACME的主管人员,但他想要开一个载货卡车而不是运动车。
幸运的是我们用的是MVC!我们需要做的所有工作就是建立一个新的ACMETruck类,包装一下,完事! public class ACME2000Truck: Automobile { public ACME2000Truck(string paramName):base(80, 25, -12, paramName){} public ACME2000Truck(string paramName, int paramMaxSpeed, int paramMaxTurnSpeed, int paramMaxReverseSpeed): base(paramMaxSpeed, paramMaxTurnSpeed, paramMaxReverseSpeed, paramName){} }
在AutoView中,我们只需要建立卡车包装一下! private void btnBuildNew_Click(object sender, System.EventArgs e) { this.autoView1.WireUp(new ACME.AutomobileControl(), new ACME.ACME2000Truck(this.txtName.Text)); }
如果我们想要一个新Control只允许我们来每次加速或减速最大5mph,小意思!做一个SlowPokeControl(和我们的AutoControl相同,但是在申请加速度中做了限制)。 public void RequestAccelerate(int paramAmount) { if(Model != null) { int amount = paramAmount; if(amount > 5) amount = 5; Model.Accelerate(amount); if(View != null) SetView(); } } public void RequestDecelerate(int paramAmount) { if(Model != null) { int amount = paramAmount; if(amount > 5) amount = 5; Model.Accelerate(amount); Model.Decelerate(amount); if(View != null) SetView(); } }
如果我们想让我们的ACME2000 Truck变得迟钝,只需要在AutoView中包装。 private void btnBuildNew_Click(object sender, System.EventArgs e) { this.autoView1.WireUp(new ACME.SlowPokeControl(), new ACME.ACME2000Truck(this.txtName.Text)); }
最后,如果我们需要一个在web上的接口,我们要做的所有工作就是建立一个Web项目在UserControl中实现IVehicleView接口。
结论
正如你所看到的,使用MVC来构建代码控制接口耦合性很低,很容易适应需求的改变。它也能使变化的影响减小,而且你可以在任何地方重用你的虚函数和接口。有很多时候我们可以在我们的项目中实现伸缩性,特别是在那些需求变化的时候,但是这需要下次再说了。
于此同时,做下一个项目的时候记住MVC...你不会感到遗憾!
你是否正在寻找适合您的.NET开发框架? 你是否是MVC结构的忠实拥护者? Mavrick.Net 是你最好的选择! Mavrick.net 是open source的.NET MVC框架软件,你可以通过 http://mavnet.sourceforge.net/ 下载最新的版本及使用说明。 以下是Mavrick.net 的一些说明: 一 系统需求: 1. 操作系统:win2000 (含IIS) 2. MS .net SDK 二 安装过程: 1. 解开下载完成的Marvice.net安装包 2. 打开IIS设置三个虚拟目录 Friendbook -> 对应 Maverick\examples\Friendbook 目录 Friendbook-xsl -> 对应 Maverick\examples\Friendbook-xsl 目录 Calendar -> 对应 Maverick\examples\Calendar目录 3. 在虚拟目录Friendbook点击鼠标右键 点击配置,在弹出窗口, 设置 可执行文件为: C:\WINNT\microsoft.NET\framework\v1.0.3705\ASPnet_isapi.dll 添加按展名: .m 4. 访问 http://localhost/Friendbook 三 系统分析 "Maverick.config"是Mavrick.net的核心,他的功能与Struct中的 struts-config.XML功能是相同的 ------------------------------------------------------------------------- ---------- <maverick version="2.0"> <commands> <command name="runQuery"> <controller class="Bar.Foo.Query, Foobar"/> <view name="success" type="document" path="queryResult.aspx"> <transfoRM type="xslt" path="lookAndFeel.xsl"/> </view> <view name="error" type="document" path="queryFailed.aspx"> <transform type="xslt" path="lookAndFeel.xsl"/> </view> </command> </commands> </maverick> ------------------------------------------------------------------------- ---------- 此例中只定义一个command 即runQuery , 它包含两种Views即 “success” 和 “error ”。 <controller class="Bar.Foo.Query, Foobar"/> 表明此command 的控制类为Bar.Foo.Query, 此类为流程控制, Mavrice.NET 将自动 调用此类中的 Perform()方法,并根据此方法的返回结果,跳转到相关的View去,执行后继的逻辑。 ------------------------------------------------------------------------- ---------- // Classname : Bar.Foo.Query.cs using System; namespace Bar.Foo { public class Query : ControllerProtected { public override string Perform() { if (loginSuccessBool) return SUCCESS; else return ERROR; } } } ------------------------------------------------------------------------- ---------- 四,部分程序代码 <!--default.aspx--> <%@ Page %> <% Context.Response.Redirect("welcome.m"); %> <!--maverick.config--> <?xml version="1.0"?> <maverick version="2.0" default-view-type="document" default-transform-type="document"> <views> <view id="loginRequired" path="loginRequired.aspx"> <transform path="trimOutside.aspx"/> </view> <view id="loginFailed" path="loginFailed.aspx"> <transform path="trimOutside.aspx"/> </view> </views> <commands> <command name="welcome"> <view path="welcome.aspx"> <transform path="trimOutside.aspx"/> </view> </command> <command name="signup"> .................. </commands> </maverick>
<!-- welcome.aspx--> <%@ Page %> <% Context.Items.Add("title","Welcome"); %> <p> Welcome to the Friendbook example. This is a simple contact-list application which demonstrates how to create a membership-based website with Maverick. </p> <% Context.Items.Add("dest","friends.m"); Context.Server.Execute("loginForm.aspx"); %>
<!--trimOutside.aspx--> <%@ Page %> <html> <head> <title> <%=Context.Items["title"]%> </title> <link rel="stylesheet" href="stylesheet.css" type="text/css" /> </head> <body> <table cellspacing="0" cellpadding="0" width="100%"> <tr> <td class="pageTitle" colspan="2"><h1><h1> <%=Context.Items["title"]%></h1></td> </tr> <tr align="center" style="text-align: center"> <td class="navigationTop"> <a class="nav" href="welcome.m">Login</a></td> <td class="navigationTop"> <a class="nav" href="signup.m">Sign Up</a></td> </tr> </table> <%=Context.Items["wrapped"]%> </body> </html> 望共同提高!
|