WinForms应用的MVP模式实践

随着WPF的流行,当团队决定为全新的桌面应用程序选择开发栈时,他们通常会选择WPF。然而,当需要升级与WinForms紧密耦合的现有软件,或者需要更高的性能时,WinForms应用程序的需求依然存在。

关于WinForms应用程序是否可测试,存在许多不同的观点。一些人认为这是不可能的,因为用户事件和业务逻辑之间存在大量的依赖。标准的Windows表单包含一个设计器文件和一个包含用户操作事件处理程序的部分类。如果遵循这一原则,并尝试在事件处理程序中实现应用程序逻辑,可以得出结论,这种应用程序很难编写测试。但是,如果从一定距离来看这个问题,可以得出结论,每个具有用户界面的应用程序都可以被表示为三个主要组件之间的交互:数据、用户界面和业务逻辑。这是MV*模式的基本概念。如果能够成功地将这三个组件分离,那么应用程序就有一个很好的测试之路。使用MVP模式制作了一个简单的WinForms应用程序。整个项目可以从提供的链接下载。

在本文中,将仅使用代码片段来给出代码的外观的一般概念。

MVP模式简介

MVP代表模型-视图-呈现器。模型是一个包含数据的组件,它只是表单的数据持有者。视图代表用户界面。它包含表单的设计描述。呈现器是WinForms应用程序中大部分工作的组件。它订阅视图事件,这些事件是用户与表单交互的结果(点击按钮、文本更改、选择更改等)以及操作系统与表单交互的结果(加载、显示、绘制等)。呈现器需要处理所有这些事件,并在处理后对视图进行适当的操作。

代码结构

让从视图组件开始。

public interface IProductView { event EventHandler ViewLoad; event EventHandler<ProductViewModel> AddNewProduct; event EventHandler<ProductViewModel> ModifyProduct; event EventHandler<int> DeleteProduct; event EventHandler<int> ProductSelected; void PopulateDataGridView(IList<Product> products); void ClearInputControls(); void ShowMessage(string message); } public partial class Products : Form, IProductView { // ... }

创建了一个接口IProductView,定义了呈现器和用户界面组件将如何交互的规则。

现在,让看看呈现器组件。

public class ProductPresenter { private IProductView view; private IProductDataAccess dataAccesService; public ProductPresenter(IProductView view, IProductDataAccess dataAccesService) { this.view = view; this.dataAccesService = dataAccesService; SubsribeToViewEvents(); } private void SubsribeToViewEvents() { view.ViewLoad += View_Load; view.AddNewProduct += View_AddNewProduct; view.ProductSelected += View_ProductSelected; view.ModifyProduct += View_ModifyProduct; view.DeleteProduct += View_DeleteProduct; } // ... }

呈现器在其构造函数中采用IProductView接口。通过这种方式,视图(表单)可以很容易地被另一个实现相同接口的视图替换。此外,当涉及到模拟时,可以很容易地通过构造函数注入这个依赖。另一个组件是IProductDataAccess,它代表数据库接口。

public interface IProductDataAccess { IList<Product> GetAllProducts(); Product GetProduct(int id); bool AddProduct(Product product); bool DeleteProduct(int productId); bool EditProduct(int productId, Product product); string ErrorMessage { get; } }

一个测试用例示例

这个测试用例展示了如何轻松地在呈现器组件中模拟外部依赖。

[Test] public void ExpectToCallAddProductOnAppropriateEventReceived() { IProductView view = Substitute.For<IProductView>(); IProductDataAccess dataAccess = Substitute.For<IProductDataAccess>(); ProductPresenter presenter = new ProductPresenter(view, dataAccess); ProductViewModel viewModel = new ProductViewModel() { NameText = "Test", PriceText = "2" }; view.AddNewProduct += Raise.Event<EventHandler<ProductViewModel>>(view, viewModel); dataAccess.Received().AddProduct(Arg.Is<Product>((x => x.Price == 2 && x.Name == "Test"))); }

这只是一个简单的例子,展示了从抽象开始的架构的强大之处。总是最好从更高层次的一些组件开始,然后,定义接口,定义了这些组件将如何相互交互的规则。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485