MVC模式的实际应用






4.55/5 (41投票s)
MVC模式的另一种方法。
目录
1. 引言
CodeProject 上有很多文章详细介绍了 MVC 模式的工作原理。那么你可能会问,为什么我们需要另一篇?在这篇文章中,我将尝试展示 MVC 模式的一个简单而清晰的应用。
该演示项目使用 ZedGraph 来生成图表(https://codeproject.org.cn/csharp/zedgraph.asp)以及一个
Juval Lovy 为 .NET 1.1 移植的 .NET 2.0 中的 BackgroundWorker
实现(http://www.idesign.net/)。
2. 背景
那么,什么是 MVC 模式?
正如 GoF 书中所述
“MVC 通过在视图和模型之间建立订阅/通知协议来解耦视图和模型。视图必须确保其外观反映模型的状态。每当模型的数据发生变化时,模型就会通知依赖它的视图。作为响应,每个视图都有机会更新自己。这种方法允许你将多个视图附加到模型以提供不同的表示。你还可以为模型创建新的视图而不必重写它。”
3. 示例
使用此模式的一个示例是报告系统,其中相同的数据需要以不同的方式呈现给用户。例如,Excel 表格可以呈现为列和行,或者使用图表。我最近遇到的此模式的另一个需求是,在桌面应用程序和为触摸屏设备开发的 GUI 中使用相同的数据。
本文中的示例显示了 listview
中的一些测试数据以及使用 ZedGraph 的图表表示。
这种设计将视图与生成测试数据的模型解耦。
4. MVC 描述
大多数现代应用程序被分为独立的层:表示层(GUI)、业务逻辑层(我们如何以及为什么做事)、数据层(持久化)。
MVC 模式的划分方式类似:
- Model(模型) – 处理应用程序使用的原始数据(计算、强制执行业务规则、查询数据库等)。模型提供的数据独立于视觉表示——因此它可以被任意数量的视图使用,而无需代码冗余!
- View(视图) – 将模型渲染成用户界面(数据的视觉表示)。视图与数据操作隔离。
- Controller(控制器) – 处理和响应事件,定义用户界面(视图)和模型如何响应用户输入的行为。用户触发更改模型的事件,模型随后通知注册的视图来更新/刷新其数据。
5. MVC 的优点
由于模型与视图解耦,因此可以极大地灵活地实现模型,从而实现代码重用和模块化。
只要输出保持不变,模型内部的工作就可以随时更改,而不会对视图产生任何影响。
模型和视图的并行开发过程!
6. 缺点
- 增加了开发的复杂性
- 不适合小型应用程序
7. 工作原理 / 解析示例应用程序
你可以从开发模型或视图开始。
让我们从描述模型开始。我为此创建了一个所有模型的 abstract
基类,如下所示:
public abstract class BaseModel
{
/// <SUMMARY>
/// Event fired when the model is finished calculating/processing the data.
/// </SUMMARY>
public event EventHandler ModelChanged;
/// <SUMMARY>
/// Event fired when the model is calculating the data to notify the view
/// about its progress.
/// </SUMMARY>
public event ProgressEventHandler ModelProgress;
#region protected members
protected BackgroundWorker backgroundWorker;
protected DataSet ds;
#endregion protected members
#region constructor
protected BaseModel()
{
ds = new DataSet();
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = false;
backgroundWorker.WorkerSupportsCancellation = true;
}
#endregion constructor
#region public abstract methods
public abstract void GenerateReport();
#endregion public abstract methods
#region public methods
/// <SUMMARY>
/// Returns the report data.
/// </SUMMARY>
public DataSet QueryModel()
{
return ds;
}
public void CancelBackgroundWorker()
{
backgroundWorker.CancelAsync();
}
#endregion public methods
#region event firing methods
protected void Fire_ModelChanged(object sender, RunWorkerCompletedEventArgs ea)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
bw.RunWorkerCompleted -=
new RunWorkerCompletedEventHandler(Fire_ModelChanged);
if (ModelChanged != null)
ModelChanged(this, EventArgs.Empty);
}
protected void Fire_ModelProgress(object sender, int percent)
{
if (ModelProgress != null)
ModelProgress(this, percent);
}
#endregion event firing methods
}
模型的基类包含一个 BackgroundWorker
和一个 dataset
对象。我使用了 Juval Lovy 修改过的 BackgroundWorker
实现。这样,我们就可以在一个单独的线程中进行计算,并拥有一个响应迅速的用户界面。dataset 简单地存储视图使用的测试数据。
Model_Income
类派生自 BaseModel
,并向 dataset
添加了一个包含两列的表,该表在重写的 GenerateReport
方法中填充了测试数据。BaseModel
暴露了两个事件:
ModelChanged
– 当模型完成生成视图所需数据时触发。ModelProgress
– 每次模型希望通知视图数据计算进度时触发。
控制器将视图订阅到模型暴露的这些事件。
模型的角色:生成数据,在需要时触发事件来通知视图其状态。
视图也派生自基类 BaseView
,该基类反过来暴露一个或多个事件以及用于从控制器调用的 public
方法。
public class BaseView : Form
{
public event EventHandler ViewClosed;
#region Windows Form Designer generated code
. . .
#endregion
#region constructor
public BaseView()
{
InitializeComponent();
this.Closed += new EventHandler(OnViewClosed);
}
#endregion constructor
#region form eventhandlers
private void OnViewClosed(object sender, EventArgs ea)
{
if (ViewClosed != null)
{
ViewClosed(this, ea);
}
}
private void butClose_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion form eventhandlers
#region public methods
/// <SUMMARY>
/// Called from the controller to update the View when the Model
/// is finished loading the data.
/// </SUMMARY>
public void UpdateView(object sender, EventArgs ea)
{
BaseModel model = sender as BaseModel;
if (model == null)
return;
DataSet ds = model.QueryModel();
ContinueUpdateView(ref ds);
progressBar.Visible = false;
}
/// <SUMMARY>
/// Called from the controller to update the View's progressbar when
/// the Model is reporting some progress.
/// </SUMMARY>
public void UpdateProgress(object sender, int percent)
{
progressBar.Visible = true;
if (percent >= 0 && percent <= 100)
{
progressBar.Value = percent;
}
else
{
progressBar.Value = 0;
}
}
#endregion public methods
#region virtual methods
/// <SUMMARY>
/// This method could be declared as abstract (and then the whole
/// BaseView class as an abstract class) but then we lose the ability to edit the
/// form in the form editor.
/// </SUMMARY>
/// <param name="ds"></param>
protected virtual void ContinueUpdateView(ref DataSet ds)
{
}
#endregion virtual methods
}
视图的实际实现覆盖了 ContinueUpdateView
,以直观地显示模型的数据。
视图的角色:查询模型数据并将其呈现给用户。
将所有这一切结合在一起的是控制器。控制器实例化模型和视图,并将它们订阅到对方暴露的事件。还有一个用于不同类型控制器的抽象基类。
public abstract class BaseController
{
protected abstract void SubscribeControllerToView();
protected abstract void SubsctribeModelToView();
}
正如代码所示,每个控制器都必须确保视图的事件被控制器捕获并传播到视图(SubscribeControllerToView
),反之亦然,模型事件被捕获并传播到视图(SubscribeModelToView
)。
为了实例化视图(在本例中为报告),我们只需实例化适当的控制器,例如:
RptController controller = new RptController(RptControllerType.List);
控制器通过传递到构造函数的参数来知道使用哪个视图和模型来创建报告。
8. 结论
我认为,如果正确使用 MVC 模式,它可以加快开发速度,并通过将 UI 层与数据层分离来简化代码复杂性。
请记住,这是我写的第一篇文章,所以如果有些句子显得冗长不清,那都是我的错。;)
9. 参考文献
10. 历史
- 2007 年 1 月 8 日:首次发布