MVC模式的介绍(C#)

 

Benefits
在开发项目中使用“模型-视图-控制器(MVC)”模式的好处在于可以完全消除商业流程和应用表达层之间的相互影响。此外,还可以获得一个完全独立的对象来控制表达层。本文项目里的这种独立性使代码的重用非常简单,代码的维护也稍微容易了一些(下面就会看到)。

通常我们都知道要让对象尽量减少之间的依赖关系,这样,我们努力编写的代码才容易修改。为了达到这种目的,需要遵循一个通行的原则采用MVC模式“在接口上编程,而不应该在类上编程”("programming to the interface, not the class" )。

我们的任务就是……

我们接了个任务——ACME 2000 赛车项目,任务就是编个简单的交互界面,实现以下功能:1、显示车辆的当前方向和速度;2、让最终用户可以改变方向、加速、减速。当然,这些功能都是有一定范围限制的。

据说如果我们在这个项目上成功了,我们最后还要开发一个接口,实现类似的程序:ACME 2 皮卡和 ACME 1 三轮车 ACME 1 。做为程序员,我们了解ACME 的管理层最后会这样说“啊,非常棒!可以放到公司的网站上吗?”考虑到这些要求,我们要开发一个容易升级的产品,这样我们才能让顾客满意,才能有饭吃,哈哈。:)

我想,正好…… “这个机会正好用来实现一下MVC模式!”

架构概述

现在我们知道要用MVC了,我们就需要知道MVC到底是什么东西。我们的项目要实现MVC模式的三部分:模型、控制器和视图。在我们的项目中,汽车就是模型,用户界面就是视图,连接这两部分的就是控制器。

我们使用控制器来操纵模型(ACME2000运动型汽车),控制器将向模型发送请求,并更新用户接口——视图。这样看上去很简单。我们需要解决的第一个问题是:用户想让汽车跑得更快或者是转弯的话,要做些什么?答案就是通过视图(我们的程序窗口),借助控制器发送一个请求。


 
我们还有一个问题需要解决:视图没有足够的信息来显示当前模型的状态。解决办法就是在图里面增加一个箭头——视图能够请求获得足够的模型状态信息来显示模型状态。

这样,用户(司机)就可以通过视图使用整个ACME汽车控制系统。如何用户想操纵这个系统,比如说加速,视图就会发出请求,并且由控制器来处理这个请求。控制器将把请求告诉模型,由模型来做出相应的动作,并且,控制器还将会更新试图。
如果一个不守规矩的司机发出加速到底的指令,汽车将高速运行,这时候,司机再发出转弯的指令,控制器就会在试图中取消转弯的功能,这样就可以防止车祸的发生。
模型(汽车)会告诉视图:速度已经升上去了,视图就会做出相应的显示。
总结一下,我们就可以预览一下下面的架构了。


 
开始编写我们的程序:

在动手之前,程序员应该做如下思考。我们的系统要足够健壮,要想到尽量多的系统可能发生的变化。我们要牢记两条黄金准则:类的松耦合,以及实现松耦合而使用的对接口编程。
所以,先增加三个接口(正如你猜到的,一个是模型的接口,一个是视图的接口,还有一个就是控制器的接口)。
在和ACME的人打过充分的交道后,我们获得了系统需求:汽车要能够前进、倒车、转弯,要设定前进、倒车、转弯时的最大速度,仪表板(视图)要能够显示当前的速度和方向。
需求很多,但我们能够搞定它……
首先来做些准备工作。我们需要创建来个枚举来表达方向和转向请求,绝对方向(AbsoluteDirection)和相对方向(RelativeDirection)。
public enum AbsoluteDirection
{
North=0, East, South, West
}
public enum RelativeDirection
{
Right, Left, Back
}

然后,处理控制器接口。控制器要告诉模型如下请求:加速、减速、转向。我们增加一个包含合适的方法的汽车控制接口(IVehicleControl)。

public interface IVehicleControl
{
void Accelerate(int paramAmount);
void Decelerate(int paramAmount);
void Turn(RelativeDirection paramDirection); 
}

接下来,处理模型接口。我们需要知道汽车的名字、速度、最大前进速度、最大倒车速车、最大转弯速度、方向。我们还需要如下方法:加速、减速、转向。

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);
}
最后,处理视图接口。我们知道,视图需要向控制器暴露一些功能调用:允许/禁止加速/减速/转向。

public class IVehicleView
{
void DisableAcceleration();
void EnableAcceleration();
void DisableDeceleration();
void EnableDeceleration();
void DisableTurning();
void EnableTurning();
}
现在,我们要调整一些这些接口,让它们可以交互。首先,控制器要知道属于它的视图和模型,所以我们在汽车

public interface IVehicleControl
{
void RequestAccelerate(int paramAmount);
void RequestDecelerate(int paramAmount);
void RequestTurn(RelativeDirection paramDirection); 
void SetModel(IVehicleModel paramAuto);
void SetView(IVehicleView paramView);
}
下一步使用了点技巧,我们使用GOF设计模式——观察器(Observer)以便视图得知模型的变化。
为了让视图能够得知模型的变化,我们需要在模型里增加如下方法来实现这个设计模式:增加观察其(AddObserver)、移除观察器(RemoveObserver)、通知观察者(NotifyObserver)。

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();

在视图中增加如下的方法(用来观察模型)。这样,模型就会有个指向视图的引用。当模型发生变化时,将会使

public class IVehicleView
{
void DisableAcceleration();
void EnableAcceleration();
void DisableDeceleration();
void EnableDeceleration();
void DisableTurning();
void EnableTurning();
void Update(IVehicleModel paramModel);
}
现在我们可以把这些接口放在一起了。只需要在剩余的代码里面使用这些接口,就可以保证松耦合(好事一件)

public abstract class Automobile: IVehicleModel
{
#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
#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
#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
}
最后……
汽车框架有了,现在要实现剩下的两个接口:控制器和模型。
现在通过实现汽车控制器接口来生成具体的汽车控制器(AutomobileControl )。汽车控制器基于模型状态来设置

public class AutomobileControl: IVehicleControl
{
private IVehicleModel Model;
private IVehicleView View;
public AutomobileControl(IVehicleModel paramModel, IVehicleView paramView)
{
this.Model = paramModel;
this.View = paramView;
}
public AutomobileControl()
{
}
#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();
}
}
}
接下来是ACME200运动鞋汽车类(继承了汽车抽象类,而汽车抽象类实现了汽车模型接口):

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){}
}
现在轮到视图了……
我们来创建MVC三个组件中的最后一个——视图。
创建一个汽车视图来实现汽车视图接口。汽车视图包含了指向控制器接口和模型接口的引用:

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);
}

接下来,增加按钮、标签来显示ACME2000运动型汽车的状态,还有一个状态栏用作填充按钮的代码。

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;
}

最后,实现汽车视图接口的方法……

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运动型汽车。一切都按照计划那样运行。接着,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){} 
}

在汽车视图里,我们只需要创建皮卡,并把它连接上去。

private void btnBuildNew_Click(object sender, System.EventArgs e)
{
this.autoView1.WireUp(new ACME.AutomobileControl(), new ACME.ACME2000Truck(this.txtName.Text));
}

如何我们要创建新的控制器,这个控制器只允许最大5mph的加减速,好办!创建一个限定加减速控制器(SlowPokeControl,和汽车控制器一样,只多了个最大加减速的限制)。

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皮卡加上限定加减速功能,再连接到汽车视图里。

private void btnBuildNew_Click(object sender, System.EventArgs e)
{
this.autoView1.WireUp(new ACME.SlowPokeControl(), new ACME.ACME2000Truck(this.txtName.Text));
}

最后,我们想创建一个基于web的界面,需要做的就是创建一个web工程,并且在用户控制器实现汽车视图接口。
总结一下……
正如我们看到的那样,使用MVC创建控制接口能够很好地实现松耦合,而且能够轻松地应对需求变化,减少需求变化带来的影响。我们还可以随处重用这些接口和抽象类。
在我们的项目里,还有几个地方可以做得更加柔性可变,特别是请求改变模型状态的实现,这些都将在下次讨论。
请在你的项目里记住MVC,你不会后悔。
驾驶快乐!

试图(每次把请求传递给模型时,都会调用SetView方法)。
注意,使用指向汽车模型接口的引用(不是汽车抽象类)来保持松耦合,汽车视图也是这样。。通过实现汽车视图接口(IVehicleView)可以生成任何显示汽车状态的人机界面,而我们的汽车ACME可以通过实现汽车模型接口(IVehicleModel)来生成,再通过实现汽车控制器接口(IVehicleControl)来生成汽车的操纵器。

接下来……哪些是我们通常要做的?

我们制作的汽车都是一样的,所以,创建一个共同的框架来处理各种操作。因为我们不想让别人驾驶一个框架,所以就要使用一个抽象类(不允许创建抽象类的实例)。我们把这个抽象类叫做汽车(Automobile)。可以使用队列列表(System.Collections中的ArrayList)来跟踪所有的视图(还记得观察者模式吗?)。当然也可以使用一个旧的汽车视图平面队列,但我们有太多的文章将这方面的东西可供参考。如果有兴趣研究汽车模型接口和汽车视图接口是如何交互的,可以研究一下如下方法的实现:AddObserver, RemoveObserver, and NotifyObservers。每当速度或方向发生变化时,模型将通知视图。用NotifyObservers方法,把指向视图的引用传给自己,并调用视图的Update方法来通知视图进行相应的变化。控制接口中增加如下方法:设置模型(SetModel)和设置视图(SetView)。

MVC模式的介绍(C#)的更多相关文章

  1. MVC模式简单介绍

    模型-视图-控件(model-View-Controller)MVC结构是一种开发模块的方法,它将数据存储和数据处理从数据的可视化表示中分离出来.存储和处理数据的组件称为模型,它包括模块的实际内容.表 ...

  2. jsp学习---mvc模式介绍和el表达式,jstl标签库的使用入门

    一.mvc模式介绍 下图是常用的mvc分层模式: 项目中的包命名规则,一般如下: com.amos.domain 封装JavaBean,一般我喜欢用model命名这个包com.amos.dao 封装d ...

  3. ASP.net MVC模式介绍(一)

    一.ASP.NET 支持三种不同的开发模式:Web Pages(Web 页面).MVC(Model View Controller 模型-视图-控制器)表现层.Web Forms(Web 窗体) mv ...

  4. JSP学习笔记(5)——Servlet、监听器、过滤器、MVC模式介绍

    MVC模式 在讲解Servlet前,先介绍一下MVC模式. M:model 模型,相当于数据层,用于存放数据,如一个Java中的一个bean类 V:view 视图,相当于页面层,用于显示数据,如一个网 ...

  5. IOS的MVC和MVVM模式简明介绍

    iOS中的MVC(Model-View-Controller)将软件系统分为Model.View.Controller三部分,结构图如下: Model: 你的应用本质上是什么(但不是它的展示方式) C ...

  6. MVC模式介绍

    MVC是一种通过3个不同部分构造一个软件或组件的理想办法: 1.模型(Model):用于存储数据的对象. 2.视图(View):为模型提供数据显示的对象. 控制器(Controller):负责具体的业 ...

  7. MVC模式 与 Model2模型 介绍

    Model1回顾 MVC模式:MVC(Model.View.Controller)是软件开发过程中比较流行的设计思想.旨在分离模型.控制.师徒.是一种分层思想的体现. Model2简介Java Web ...

  8. MVC 模式介绍(1)

    MVC 模式 MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式.这种模式用于应用程序的分层开发. Model(模型) - 模型代表一个存取数据的对象或 JAVA ...

  9. ASP.Net MVC开发基础学习笔记:一、走向MVC模式

    一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...

随机推荐

  1. 创建 DLL 步骤 和 SRC

    LIBRARY SimulationTouchDll EXPORTS MouseControl GetPosition //MouseControlInterface.def 文件 #pragma o ...

  2. ASP 用隐藏域解决Http无状态问题

    <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head>    < ...

  3. WPF 启动唯一程序(项目,exe,实例)

    原文:WPF 启动唯一程序(项目,exe,实例) 描述:用户多次快速开启WPF程序的时候  只运行起来 一个 程序(exe) 其他多开的 进程 自动关闭掉 App.xaml.cs文件 protecte ...

  4. ArcGIS数据生产与精细化制图之中国年降水量分布图的制作

    原文:ArcGIS数据生产与精细化制图之中国年降水量分布图的制作 楼主按:在今年的Esri中国用户大会上,我听了几场关于ArcGIS用于制图方面的讲座,也在体验区与Esri中国的技术老师有一些交流.一 ...

  5. 零元学Expression Blend 4 - Chapter 28 ListBox的基本运用与更改预设样式

    原文:零元学Expression Blend 4 - Chapter 28 ListBox的基本运用与更改预设样式 本章将先教大家认识ListBox的基本运用与更改预设样式 本章将先教大家认识List ...

  6. vs编译在win xp电脑上运行的win32程序遇到的问题记录(无法定位程序输入点GetTickCount64于动态链接库KERNEL32.dll)

    直接编译后运行,弹出提示框:不是有效的win32应用程序 像之前那样把msvcr110.dll复制过去依然报错: 这是因为vs2012编译的win32程序用到的系统函数在xp环境上对应不上.之前转载的 ...

  7. Docker笔记01-发布一个dotnetcore应用

    OS:Widows 10 IDE: VS2017 Docker:Docker Desktop for Windows Windows下安装Docker需要先启用Hyper-v 在Windows 容器的 ...

  8. 【MyEclipse常见错误】-java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory的解决

    ApacheJavaTomcatMyeclipse  自己前一段时间出现了这个问题,通过在网上搜索,大概知道了原因,整理下一,以供大家参考. 将项目部署好后,启动tomcat后报错,java.lang ...

  9. 从零开始人工智能AI(一)-k-nn-矩阵

    参考资料: https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5/18069?fr=aladdin http://blog.csdn.net/c4064957 ...

  10. C语言实现常用排序算法——插入排序

    插入排序是最基础的排序算法,原理: 首先1个元素肯定是有序的,所以插入排序从第二个元素开始遍历:内循环首先请求一个空间保存待插入元素,从当前元素向数组起始位置反向遍历:当发现有大于待插入元素的元素,则 ...