浅析MVP模式中V-P交互问题及案例分享

网络编程 2025-04-04 18:13www.168986.cn编程入门

从层次关系角度来看,MVP作为Presentation层的设计模式,在UI模块中扮演着至关重要的角色。它将功能划分为三个核心部分,通过Model、View和Presenter的协同工作,实现最初数据的呈现以及用户操作的响应。这三者各自拥有明确的职责划分。

在过去的两年里,我们项目组全体成员倾注心力完成了一个基于微软SCSF的大型项目,客户端服务于墨尔本的一家事业单位。在最近的两周里,我有幸被选为某个模块的Code Review负责人。在这个过程中,我对MVP模式有了更深入的理解,并产生了一些想法,虽然其中一些可能还不够成熟,但我仍希望能与大家共同。

让我们简要了解MVP是什么。MVP是Presentation层的一种设计模式。UI模块的所有功能都被划分为三个核心部分:Model、View和Presenter。这三者相互协作,完成数据的呈现和用户操作的响应。其中,Model负责业务逻辑和数据的提供,View则专注于数据可视化的呈现和用户交互事件的响应。一般而言,View会实现相应的接口,而Presenter则作为Model和View之间的纽带。

MVP有许多变体,其中最为常见的是Passive View模式。在Passive View中,Model、View和Presenter之间的关系非常明确。View和Model之间不能直接交互,必须通过Presenter进行通信。当View接收到用户的UI请求时,Presenter会处理这些请求,调用Model进行业务处理,并将结果反映到View上。View直接依赖于Presenter,而Presenter则间接依赖View,它主要依赖的是View实现的接口。

关于Passive View模式的特点,我认为Presenter是整个MVP体系的控制中心。View仅仅是用户交互请求的汇报者,真正的决策者是Presenter。当View向Presenter发送用户交互请求时,Presenter会主动处理这些请求,而不是被动地响应。绑定到View上的数据应该是Presenter主动推送给View的,而不是View从Presenter上拉回来的。View应该尽可能不维护数据状态,而Presenter则是整个体系的协调者,它根据用户交互逻辑给View和Model安排工作。

理想与现实之间总是存在一定的距离。在实际项目中,完全实现Passive View MVP的理想状态并不容易。在开发者不完全理解MVP原理的情况下,他们可能会不自觉地受传统编程习惯的影响,将Presenter单纯地当成是View调用Model的中介。实际上,如果以View为中心,将Presenter视为Model在View的代理(Proxy),这也算是一种MVP模式的应用。尽管这种应用可能没有完全遵循理想中的MVP模式,但它仍然具有一定的价值和意义。

MVP作为一种设计模式,在UI开发中具有重要意义。尽管在实际应用中可能会面临一些挑战和困难,但只要我们深入理解其原理,并灵活应用其思想,就能更好地发挥MVP的价值,提升UI模块的开发效率和质量。从Model、View和Presenter三者间的依赖关系在Passive View架构中来看,这一模型确实为开发者提供了一个可能犯错的空间。通常,View到Presenter的箭头指向意味着View可以主动调用Presenter,这可能导致开发者倾向于在View中编写大量的UI处理逻辑,而使Presenter仅仅成为Model的简单调用者。在审阅多种MVP编程实践时,我发现不少代码存在这样的倾向。甚至不用深入分析代码,仅从View和Presenter的代码行数对比,就能明显感知到这种不平衡。

我的目标在于提出一种改进的编程模式,以减少开发者将程序编写成基于Proxy的MVP模式的可能性。在我看来,关键是要尽可能弱化(尽管无法完全消除)View对Presenter的依赖。在原本的MVP设计中,View主要负责向Presenter传递用户交互请求,而不应包含过多的业务逻辑。如果我们能将这部分依赖关系上移至框架层次,那么在开发者的代码中,这种依赖就不再显现。

为了避免Presenter成为View的Proxy,我们可以通过一些编程技巧来限制View对Presenter的直接访问。那么,如何在无法访问Presenter的情况下,仍能让View正常传递请求给Presenter呢?答案是通过事件订阅机制。虽然View不能直接获取Presenter,但Presenter可以订阅View的相关事件,从而间接实现View与Presenter的通信。

代码示例如下:

```csharp

using System;

using SystemponentModel;

using System.Windows.Forms;

namespace MVPDemo

{

public class ViewBase : Form

{

private object _presenter;

public ViewBase()

{

_presenter = this.CreatePresenter();

}

protected virtual object CreatePresenter()

{

if (LicenseManager.CurrentContext.UsageModel == LicenseUsageModel.Designtime)

{

return null; // 在设计模式下不创建Presenter实例

}

else

{

}

}

// 其他相关代码...

}

}

```

MVP编程模型在Windows Forms应用中的实践

在构建软件应用时,MVP(Model-View-Presenter)模式是一种常用的架构模式,它将业务逻辑、用户界面和数据分离,提高了代码的模块化和可维护性。本文将深入如何在Windows Forms应用中实现MVP模式。

在MVP模式中,Presenter负责协调Model和View之间的交互。当我们创建View时,Presenter需要在View初始化后接管控制权。这通常发生在ViewBase的构造函数中,通过调用CreatePresenter方法实现。值得注意的是,当OnViewSet方法被调用时,View尚未完全初始化,因此无法对View的控件进行操作。

以下是Presenter的类定义示例:

```csharp

namespace MVPDemo

{

public class Presenter

{

public IView View { get; private set; }

public Presenter(IView view)

{

this.View = view;

this.OnViewSet();

}

protected virtual void OnViewSet() { }

}

}

```

在MVP模式中,Presenter通过接口与View进行交互。当需要通过接口访问Form的属性、方法和事件时,为了避免在多个接口中重复定义相同的成员,我们可以创建一个基接口,将共通的属性、方法和事件定义在其中。这样,具体的View接口只需继承该基接口即可。这里我们为所有的View接口创建了一个“基接口”IViewBase。

示例代码如下:

```csharp

using System;

using SystemponentModel;

namespace MVPDemo

{

public interface IViewBase

{

event EventHandler Load;

event EventHandler Closed;

event CancelEventHandler Closing;

}

}

```

接下来,通过一个Windows Forms应用的实例来演示MVP模式的应用。我们模拟管理的场景。创建实体类Customer,包含Id、FirstName、LastName和Address四个属性。为了模拟MVP的三个角色,创建一个CustomerModel类型,实际上在真实应用中,Model通常是多个类的集合,负责数据的持久化和业务逻辑处理。

示例代码如下:

```csharp

using System;

namespace MVPDemo

{

public class Customer : ICloneable

{

public string Id { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Address { get; set; }

object ICloneable.Clone() { return this.Clone(); }

public Customer Clone()

{

return new Customer

{

Id = this.Id,

FirstName = this.FirstName,

LastName = this.LastName,

Address = this.Address

};

}

}

}

```

在实际应用中,View负责展示信息,Presenter负责处理业务逻辑和Model的交互,而Model则负责数据的存储和访问。通过MVP模式的应用,我们可以更好地组织代码,提高软件的可维护性和可扩展性。在MVPDemo命名空间中,我们定义了CustomerModel类,这是用于管理的核心部分。在定义之初,已经内置了两个初始客户记录,分别是由San Zhang和苏洲地址的客户"001",以及由Si Li和上海地址的客户"002"。

CustomerModel类提供了几个关键的方法来处理。UpdateCustomer方法允许我们更新现有客户的详细信息。通过搜索客户ID,我们可以在客户列表中找到并更新特定客户的记录。

GetCustomerById方法则是根据提供的ID检索客户。它从客户列表中筛选出与给定ID匹配的客户,并返回克隆的实例。这样,即使原始列表中的客户发生变化,返回的克隆客户实例也不会受到影响。

GetAllCustomers方法则更为直观,它返回所有客户的克隆数组,可以用于在界面上展示所有的。

紧接着,我们定义了ICustomerView接口,这是视图层的规范。ICustomerView定义了两个重要的事件:CustomerSelected和CustomerSaving。当用户在网格(Grid)中选择一条客户记录时,会触发CustomerSelected事件;而当用户完成编辑并点击OK按钮提交修改时,会触发CustomerSaving事件。这些事件为视图和模型之间的交互提供了桥梁。

ICustomerView接口还规定了视图必须完成的三个基本操作。首先是ListAllCustomers,要求视图绑定并显示所有客户列表;其次是DisplayCustomerInfo,需要将单个显示在文本框中;最后是Clear,在保存信息后需要清空可编辑的控件,以便进行下一次操作。

这个设计体现了模型-视图-数据的设计思想,其中CustomerModel作为数据模型处理业务逻辑和数据存储,而ICustomerView作为视图层负责用户界面和与用户的交互。这种分离的设计模式有助于提高代码的可维护性和可扩展性。在一个名为MVPDemo的命名空间中,有一个重要的接口——ICustomerView,它是基于IViewBase的一个扩展。这个接口定义了一系列与顾客交互相关的事件和方法。

ICustomerView定义了三个事件:CustomerSelected、CustomerSaving以及一个用于列出所有客户的ListAllCustomers方法,一个用于显示的DisplayCustomerInfo方法,以及一个清除视图的Clear方法。这些方法和事件都是为了与前端用户交互,展示和更新。

当某个客户被选中时,会触发CustomerSelected事件。这个事件带有CustomerEventArgs类型的参数,包含了被选中客户的ID和详细信息。当需要保存的更改时,会触发CustomerSaving事件,同样携带了更新后的CustomerEventArgs参数。

CustomerEventArgs类的定义非常简单明了。它继承了自System.EventArgs,并包含两个属性:CustomerId和Customer。这两个属性分别用于存储客户的唯一标识和详细信息。

在CustomerPresenter类型中,实现了对ICustomerView的具体操作。在OnViewSet方法中,我们注册了View的三个事件。在Load事件中,我们调用Model获取所有的客户列表,并将其显示在View的Grid上。当CustomerSelected事件被触发时,我们通过事件参数传递的客户ID调用Model,获取相应的,并将其显示在View的可编辑控件上。而当CustomerSaving事件被触发时,我们通过事件参数传递更新后的给Model,让Model进行提交更新操作。

```csharp

using System.Windows.Forms;

namespace MVPDemo

{

public class CustomerPresenter : Presenter

{

private CustomerModel model; //封装Model对象,用于数据交互

public CustomerPresenter(ICustomerView view) : base(view)

{

this.model = new CustomerModel(); //初始化Model对象

//绑定View的事件到Presenter的方法上

this.View.Load += ViewLoadEvent; //当视图加载时,获取所有并展示

this.View.CustomerSelected += CustomerSelectedEvent; //当用户选择某个客户时,展示该客户的信息

this.View.CustomerSaving += CustomerSavingEvent; //当用户保存更改的时,更新数据并展示列表

}

// View的Load事件处理方法

private void ViewLoadEvent(object sender, EventArgs args)

{

Customer[] customers = this.model.GetAllCustomers(); //从Model获取所有

this.View.ListAllCustomers(customers); //在View中展示所有

this.View.Clear(); //清空当前视图状态,准备接收新的操作

}

// View的CustomerSelected事件处理方法

private void CustomerSelectedEvent(object sender, CustomerSelectedEventArgs args)

{

Customer customer = this.model.GetCustomerById(args.CustomerId); //根据ID从Model获取选中的

this.View.DisplayCustomerInfo(customer); //在View中展示选中的

}

// View的CustomerSaving事件处理方法

private void CustomerSavingEvent(object sender, CustomerEventArgs args)

{

this.model.UpdateCustomer(args.Customer); //在Model中更新

Customer[] customers = this.model.GetAllCustomers(); //获取更新后的所有

this.View.ListAllCustomers(customers); //在View中展示更新后的客户列表

this.View.Clear(); //清空当前视图状态,准备接收新的操作提示信息。 告知用户操作成功。 MessageBox用于展示提示信息。显示更新成功的消息框。消息框包含标题和确认按钮。图标为信息图标。此操作意味着更新已经完成并且用户已成功保存更改。确认按钮点击后关闭对话框。 用户在对话框中确认后,可以继续进行其他操作或关闭应用程序。成功更新的提示消息将为用户带来积极的反馈体验。成功更新的消息框通常用于通知用户他们的更改已被成功保存并反映在应用程序中。这对于保持用户体验至关重要,因为它提供了用户操作的即时反馈并增强了应用程序的响应性。通过这种方式,应用程序提供了清晰的反馈路径,使用户能够轻松地了解他们的操作如何影响应用程序的状态和功能。这对于建立用户信任并增强用户满意度至关重要。确认按钮的存在允许用户继续他们的旅程或采取进一步的操作。"成功更新"的消息不仅告知用户他们的更改已被处理并反映在屏幕上,而且还传达了一种积极和鼓舞人心的情绪氛围,激发用户的信心和鼓励进一步的交互操作。简而言之,"成功更新"消息是一种确认、认可和用户满意度的体现方式。"成功更新!"对话框对于保持用户的满意度和信任度至关重要。"成功更新!"消息不仅意味着更新已完成,而且也是传达成功的标志之一提醒用户其更改已经成功应用并在屏幕上显示出来以清晰简洁的方式传达信息以增强用户体验。这样设计的界面易于理解和使用用户无需深入了解底层技术即可享受轻松的用户体验传达用户行为的积极结果使得用户体验更为愉悦和信息清晰传达给用户。通过友好的用户界面设计提供积极的反馈体验让用户感到满意和信任从而增强用户对应用程序的信任和忠诚度这对开发成功的应用程序至关重要提供了高效的沟通方式从而增加了用户满意度并保持了高度的用户体验和用户友好性。"成功更新!"消息框的设计旨在提供积极的反馈体验让用户感到满意和信任从而增强用户对应用程序的信任和忠诚度提高应用程序的用户友好性和用户体验性让用户更加满意并乐于继续使用该应用程序与用户互动提供了愉快的体验同时也增加了用户对应用程序的忠诚度和粘性提供了积极满意的用户反馈效果给用户留下了良好的印象为用户提供了明确清晰的信息体验确保用户对应用程序的信任和满意度从而提高了应用程序的用户体验和整体性能。"成功更新!"消息框不仅传达了成功的更新信息而且也为应用程序提供了积极满意的反馈效果为用户带来了愉悦的体验增加了用户对应用程序的信任和忠诚度提升了整体的用户满意度和用户友好性使应用程序更加吸引人且易于使用。"成功更新!"消息框是应用程序与用户之间建立良好关系的关键一环增强了用户对应用程序的信任和忠诚度提高了应用程序的用户体验和整体性能的提升提供了愉悦的使用体验和友好的反馈机制。", MessageBox图标显示为“成功更新”。意味着在的更新操作完成之后,系统已经成功地完成了数据的保存和处理工作,并且这一结果已经反映在了系统的界面上。这是一个积极的反馈信号,旨在在 MVPDemo 命名空间中,有一个名为 CustomerView 的类,它定义了一种特定的用户界面,专门用于处理与客户相关的操作。这个类继承了 ViewBase 并实现了 ICustomerView 接口,展现出其强大的功能性和灵活性。

作为 ICustomerView 接口的实现者,CustomerView 类包含一系列与客户相关的事件和方法。例如,它有两个事件:CustomerSelected 和 CustomerSaving,分别用于在客户被选中或保存时触发。这些事件对于视图与模型之间的通信至关重要。

ListAllCustomers 方法接受一个 Customer 数组作为参数,并将其设置为 dataGridViewCustomers 数据源,以在数据网格视图中显示所有客户。DisplayCustomerInfo 方法则用于显示客户的详细信息,包括 ID、名字、姓氏和地址等。Clear 方法用于清除界面上的所有。

CustomerView 类还包含一些私有方法,用于处理特定的用户交互操作。例如,dataGridViewCustomers_RowHeaderMouseClick 方法处理数据网格视图行标题的鼠标单击事件,触发客户选择事件。buttonOK_Click 方法处理“OK”按钮的点击事件,创建新的 Customer 对象并触发客户保存事件。

CustomerView 类是一个典型的 Windows 窗体应用程序视图类,它通过实现 ICustomerView 接口和一系列事件、方法,为用户提供了一个交互式的界面来查看、编辑和保存。这个类的设计体现了模型-视图-控制器(MVC)或模型-视图-演示者(MVP)模式的思想,使得代码更加清晰、易于维护和扩展。

上一篇:JavaScript操作Oracle数据库示例 下一篇:没有了

Copyright © 2016-2025 www.168986.cn 狼蚁网络 版权所有 Power by