模型视图控制器,以编写易于维护和扩展的应用程序





5.00/5 (2投票s)
本文解释了如何使用 MVC 编写易于维护和扩展的应用程序。
引言
好吧,我们每天都在编写代码,而且很多时候我们都觉得应该有一种更好的方式来编写代码。最有可能的是一种易于维护和易于扩展的代码。当我们编写代码时,最终会编写紧耦合的代码,所以如果有新的需求出现;我们必须修改现有代码,这样我们的代码就不是为了修改而关闭的。要记住一条基本原则:“代码应该为了扩展而关闭修改。” 所以,牢记这句话,我将编写一个小应用程序,它将说明为了准备好修改和允许在不更改代码的情况下进行扩展需要做什么。我将尝试采用著名的“MVC”方法来分离不同的层,以便读者理解它如何使日常编码受益。
背景
通常,当我们获得需求时,我们会开始编写代码,而不会过多地考虑如果出现新需求或新客户想要一些不同的东西会怎样。
Using the Code
要求
在这里,我将编写一个小应用程序,它展示了使用面向对象概念的好处。我们有一个需求,需要根据部门过滤来显示员工数据。有一个要求是,此数据可以在网格中显示,并且可以使用组合框来过滤记录。还有一个列表框控件,显示网格的第一列,即我们的情况下的员工姓名。因此,在组合框项更改时,网格将加载过滤后的员工数据,并且在选择网格行时,列表框将加载属于部门 ID 的员工姓名。
初看解决方案
好吧,没大问题,让我们创建一个窗口应用程序,并创建一个数据层,其中包含加载组合框的数据,以及基于组合框指定的过滤条件要显示在网格中的数据集合。将一个组合框、一个网格和一个列表框拖放到窗口表单上。使用数据加载组合框,并在组合框的 SelectedIndexChanged 事件中加载过滤后的数据到网格中。
private void cmbEntity_SelectedIndexChanged(object sender, EventArgs e)
{
if(null != cmbEntity.SelectedItem){
int val = Convert.ToInt16(cmbEntity.SelectedItem);
List<Entity> entities = objEntData.GetAllData(val);
// bind the grid with employee belonging to selected department
dgEntityView.DataSource = entities;
}
void dgEntityView_RowEnter(object sender, DataGridViewCellEventArgs e)
{
lstBoxEntity.Items.Clear();
foreach (EmployeeEntity item in entities)
{
// add the employee name to lisbox
lstBoxEntity.Items.Add(item.EmployeeName);
}
}
所以在这里你可以看到,当事件触发时,我们根据组合框选定的项加载数据并加载网格,当网格选择行事件发生时,我们加载列表框中的员工姓名。
紧耦合[不是扩展的好迹象]
这种方法的缺点
设计冻结后,另一位客户来了,要求在 Web 上提供新的视图,而不是窗口。这位客户还希望数据以图表而不是网格的形式显示。所以,没问题,让我们创建一个另一个 Web 应用程序。当然,会有很多冗余代码,并且维护这些多个应用程序将是一场噩梦 [考虑到大型企业应用程序]。因此,这种情况将继续下去,直到新客户要求在 WPF 等上实现此功能。
我们必须考虑一个更好的设计
设计应该能够降低维护成本,并且设计应该能够轻松地移植到其他应用程序类型。
更好的方法
从需求来看,视图似乎是要改变的[可能是 WebForm/WPF 页面等]。所以,让我们采用一个可以满足所有这些需求的设计。
我们的对象应该是松耦合的,或者换句话说,应该将一个层与另一个层分离,以实现未来的扩展和更好的可维护性。
是否有适合我们需求的模式?
MVC - 模型-视图-控制器
模型
实现观察者模式/通知所有订阅者更改的数据或更改的状态
模型通知订阅获取通知的视图
视图
显示给用户,处理用户操作。[点击按钮/从组合框选择项等。
控制器 (Controller)
接收用户输入并请求模型对视图请求采取行动。更改状态/返回数据。
控制器也可以要求视图根据视图输入进行更改[例如启用/禁用控件。]
视图只关心表示,控制器关心将用户输入转换为模型上的操作。模型负责将更改后的数据通知所有订阅者。
代码需要进行哪些更改
让我们创建一个名为 MVC 的类库项目,并添加名为 Controller、Data、Interface、Model 和 RequestHandlers 的新文件夹。
Data
添加包含用于演示目的的数据的类。这里我创建了 EmployeeEntity 和 Department,它们具有某些属性。另一个名为 EntityDataRepositery 的类创建了用于演示的集合。
接口
/// This interface is used to make an entity Observer for any model state requested public interface IDataObserver { void Update(object data); string SubscriptionKey { get;} string ObserverKey { get; } } /// This interface is responsible for registering and updating the registered observer with the asked data. public interface IModel { void GetData(int id,string key); // the below methods register subscriber whoever want to get notified void RegisterObserver(IDataObserver dataObserver, string key); // the below methods remove passed registerd subscriber void RemoveObserver(IDataObserver dataObserver); } /// This interface works as mediator between view and model. /// It intercepts the request and ask model to provide the information to view. public interface IController { void GetEntity(int entityId, string key); }
模型
DataModel 类负责实现 IModel 接口,并在请求任何数据时通知注册的观察者。
public sealed class DataModel : IModel
此类必须实现 IModel 接口
/// This method is used to register the observer /// <param name="dataObserver">is the request handler that want to subscribe to key</param> /// <param name="key">key to which subscription happen</param> public void RegisterObserver(IDataObserver dataObserver, string key) { if (!dataObservers.Contains(dataObserver)) { dataObservers.Add(dataObserver); } } /// This mehtod is used to notify all the observers who has subscribed to model change for passed key void Notify(int id, string key) { foreach (var item in dataObservers) { if (((IDataObserver)(item)).ObserverKey == key) ((IDataObserver)(item)).Update(dataObject.GetAllData(id)); } }
控制器 (Controller)
EntityController 类实现 IController 接口。它有一个 dataModel 对象,用于在视图请求数据时使用,控制器拦截请求并将其发送到模型以通知注册的观察者。
public class EntityController : IController { IModel dataModel; public EntityController(IModel model) { this.dataModel = model; } // this method call the GetData on model for the specified subscriber. public void GetEntity(int entityId, string key) { dataModel.GetData(entityId,key); } }
ReqHandler
它有一个 BaseReqH 类,这个类用于拥有 Model 和 Controller 的引用,供子类访问。这个类有一个特殊目的,可以避免子类创建 model/controller 的对象。任何派生自 BaseReqH 的类都不需要创建这些实例。
public BaseReqH() { model = model ?? new DataModel(); controller = controller ?? new EntityController(model); }
经过所有基础工作的努力后,有趣的时刻开始了。
那么,这如何有助于创建不同的视图而不影响现有基础类或通过扩展基础类呢?
让我们创建一个新的窗口应用程序,并在窗口表单上拖放一个组合框、一个数据网格和一个列表框。
我们需要用部门加载组合框,并且数据网格应该只加载所选部门的员工。另外,当用户选择数据网格行时,列表框应该加载数据网格单元格中取自所选部门 ID 的相应员工姓名。
为了实现这一点,DatagridReqH 应该订阅 ComboReqH,而 ListBoxReqH 应该订阅控制器 DatagridReqH。操作会调用模型来通知所有匹配键的订阅者。
- 当用户在组合框中选择部门时,控制器传递 departmentId 和订阅键。
void cmb_SelectedValueChanged(object sender, EventArgs e) { controller.GetEntity(Convert.ToInt16(cmb.SelectedValue), this.SubscriptionKey); }
- DatagridReaH 是 ComboReqH 的观察者,并且也实现了 IDataObserver。它使用 Observer Key 向模型注册
model.RegisterObserver(this, ObserverKey);
- EntityController 调用 datamodel 并传递所需数据。
public void GetEntity(int entityId, string key) { dataModel.GetData(entityId, key); }
- DataModel getData 调用 Notify 方法,检查谁是需要更新的订阅者,并调用所有订阅者的 Update 方法
void Notify(int id, string key) { foreach (var item in dataObservers) { if (((IDataObserver)(item)).ObserverKey == key) ((IDataObserver)(item)).Update(dataObject.GetAllData(id)); } }
- DataGridReqH 是订阅者之一,其 update 方法被调用并加载 datagrid
public void Update(object data) { dgView.DataSource = data; }
- 现在您需要在 Window form 构造函数、Web form 构造函数或 MainWindow WPF 构造函数中实例化不同的请求处理程序
在窗口应用程序构造函数中实例化以下请求处理程序
BaseReqH bs = new BaseReqH(); ComboReqH creq = new ComboReqH(cmbEntity); DataGridReqH dreq = new DataGridReqH(dgEntityView); ListBoxReqH lreq = new ListBoxReqH(lstBoxEntity);
运行应用程序,您将看到使用编写的几个请求处理程序和 MVC 方法实现的期望功能。只是为了补充一点,别忘了观察者模式在此应用程序中的作用。
看看我们编写了 MVC 框架后,构建 UI 是多么容易。假设现在我们需要在 ASP.NET 应用程序中将数据以图表而不是网格的形式显示,并且在部门 ID 更改时显示。
- 创建一个新的 Web 应用程序,在 WebForm 上拖放下拉列表和图表。
- 图表应该派生自 WebReqH,并且也是组合框的订阅者。
- 每当 departmentid 更改时,请求就会转到控制器,控制器会要求模型更新匹配的订阅者,即我们这里的图表。
- 在 Web form 的 PageLoad 中实例化所需的请求处理程序。
我希望您喜欢阅读本文,如果您喜欢,请不要忘记评分并留下您的评论。
编程愉快 J