




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、NET平台上的Model-View-Presenter模式实践2010-01-28 21:48 by EricZhang(T2噬菌体), 3889 visits, 网摘, 收藏, 编辑 为什么要写这篇文章 笔者当前正在负责研究所中一个项目,这个项目基于.NET平台,初步拟采用C/S部署体系,所以选择了Windows Forms作为其UI。经过几此迭代,我们发现了一个问题:虽然业务逻辑已经封装到Services层中,但诸多的UI逻辑仍然弥漫在各个事件Listener中,使得UI显得臃肿不堪,并且存在诸多重复性代码。另外,需求提供方说,根据实际需要,不排除将部署结构改为B/S的可能性,甚至可能会要
2、求此系统同时支持C/S和B/S两种部署方式。那么,如果保持目前将UI逻辑编码到Windows Forms中的方式,到时这些UI逻辑将无法复用,修改部署方式的代价很大。 为了解决以上两个问题,笔者和相关人员商量后,决定引入既有成熟模式,重新设计表示层的架构方式,并重构既有代码。 提到表示层(Presentation Layer)的模式,我想大家脑海中第一个闪过的很可能是经典的MVC(Model-View-Controller)。我最初也准备使用MVC,但经过分析和实验后,我发现MVC并不适合目前的情况,因为MVC的结构相对复杂,Model和View之间要实现一个Observer模式,并实现双向通
3、信。这样重构起来Services层也必须修改。我并不想修改Services层,而且我想将View和Model彻底隔离,因为我个人并不喜欢View和Model直接通信的架构方式。最终,我选择了MVP(Model-View-Presenter)模式。 经过两天的重构和验证,目前已经将MVP正式引入项目的表示层,并且解决了上文提到的两个问题。在这期间,积累了少许关于在.NET平台上实践MVP的经验,在这里汇集成此文,和朋友们共享。UI与P Logic 首先,我想先明确一下UI和P Logic的概念。 表示层可以拆分为两个部分:User Interface(简称UI)和Presentation Log
4、ic(简称P Logic)。 UI是系统与用户交互的界面性概念,它的职责有两个接受用户的输入和向用户展示输出。UI应该是一个纯静态的概念,本身不应包含任何逻辑,而单纯是一个接受输入和展示输出的“外壳”。例如,一个不包含逻辑的Windows Form,一张不包含逻辑的页面,一个不包含逻辑的Flex界面,都属于UI。 P Logic是表示层应有的逻辑性内容。例如,某个文本内容不能为空,当某个事件发生时获取界面上哪些内容,这都属于P Logic。应该指出,P Logic应该是抽象于具体UI的,它的本质是逻辑,可以复用到任何与此逻辑相符的UI。 UI与P Logic之间的联系是事件,UI可以根据用户的
5、动作触发各种事件,P Logic响应事件并执行相应的逻辑。P Logic对UI存在约束作用,P Logic规定一套UI契约,UI要根据契约实现,才能被相应的P Logic调用。 下图展示了UI与P Logic的结构及交互原理。图1、UI与P LogicModel-View-Presenter模式 MVP模式最早由Taligent的Mike Potel在MVP: Model-View-Presenter The Taligent Programming Model for C+ and Java(点击这里下载)一文中提出。MVP的提出主要是为了解决MVC模式中结构过于复杂和模型-视图耦合性过高的
6、问题。MVP的核心思想是将UI分离成View,将P Logic分离成Presenter,而业务逻辑和领域相关逻辑都分离到Model中。View和Model完全解除耦合,不再像MVC中实现一个Observer模式,两者的通信则依靠Presenter进行。Presenter响应View接获的用户动作,并调用Model中的业务逻辑,最后将用户需要的信息返回给View。 下图直观表示了MVP模式:图2、MVP模式 图2清楚地展示了MVP模式的几个特点: 1、View和Model完全解耦,两者不发生直接关联,通过Presenter进行通信。 2、Presenter并不是与具体的View耦合,而是和一个抽
7、象的View Interface耦合,View Interface相当于一个契约,抽象出了对应View应实现的方法。只要实现了这个接口,任何View都可以与指定Presenter兼容,从而实现了P Logic的复用性和视图的无缝替换。 3、View在MVP里应该是一个“极瘦”的概念,最多也只能包含维护自身状态的逻辑,而其它逻辑都应实现在Presenter中。 总的来说,使用MVP模式可以得到以下两个收益: 1、将UI和P Logic两个关注点分离,得到更干净和单一的代码结构。 2、实现了P Logic的复用以及View的无缝替换。在.NET平台上实现MVP模式 这一节通过一个示例程序展示在.N
8、ET平台上实现MVP的一种实践方法。本来想通过我目前负责的实际项目中的代码片段作为Demo,但这样做存在两个问题:一是这样做可能会违反学校的保密守则,二是这个项目应用了许多其他框架和模式,如通过Unity实现依赖注入,通过PostSharp实现AOP来负责异常处理和事务管理等,通过NHibernate实现的ORM等等,这样如果读者不了解系统整体架构就很难完全读懂代码片段,MVP模式不够突出。因此,我专门为这篇文章实现了一个Demo,其中的MVP实践方式与实际项目中是一致的,而且Demo规模小,排除了其他干扰,使得读者更容易理解其中的MVP实现方式。 这个简单的Demo运行效果如下:图3、Dem
9、o界面 这个Demo的功能如下:这是一个简单的点餐软件。系统中存有餐厅所有菜品的信息,客户只需在界面右侧输入菜品名称和数量,单击“添加”按钮,菜品就会被添加到左侧点餐列表,并显示此菜品详细信息。如果所点菜品不存在则软件会给出提示。另外,在左侧已点餐品列表中右键单击某个条目,在弹出菜单中点击“删除”,则可将此菜品从列表删除。 下面分步骤介绍应用了MVP模式的实现方式。第一步,解决方法及工程结构 这个Demo共有三个工程,MVPSimple.Model为Mock方式实现的Services,作为Model;MVPSimple.Presenters为Presenter工程,其中包括Presenter和
10、View Interface;MVPSimple.WinUI为View的Windows Forms实现。第二步,构建Mock方式的Services 因为重点在于表示层,所以这里的Services使用了Mock方式,并没有包含真正的业务领域逻辑。其中MVPSimple.Model工程里两个文件的代码如下: FoodDto.cs:view source print?01using System; 0203namespace MVPSimple.Model 04 05/ 06/ 表示菜品类别的枚举类型 07/ 08public enum FoodType 09 10主菜 = 1, 11汤 = 2,
11、12甜品 = 3, 13 1415/ 16/ 菜品的Data Transfer Object 17/ 18public class FoodDto 19 20/ 21/ ID,标识字段 22/ 23public Int32 ID get; set; 2425/ 26/ 菜品名称 27/ 28public String Name get; set; 2930/ 31/ 菜品类型 32/ 33public FoodType Type get; set; 3435/ 36/ 菜品价格 37/ 38public Double Price get; set; 3940/ 41/ 点菜数量 42/ 43p
12、ublic Int32 Amount get; set; 44 45 FoodServices.cs:view source print?01using System; 02using System.Collections.Generic; 0304namespace MVPSimple.Model 05 06/ 07/ 菜品Services的Mock实现 08/ 09public class FoodServices 10 11private IList foodList = new List(); 1213/ 14/ 默认构造函数,初始化各个菜品 15/ 16public FoodServ
13、ices() 17 18this.foodList.Add( 19new FoodDto() 20 21ID = 1, 22Name = 牛排, 23Price = 60.00, 24Type = FoodType.主菜, 25 26); 2728this.foodList.Add( 29new FoodDto() 30 31ID = 2, 32Name = 法式蜗牛, 33Price = 120.00, 34Type = FoodType.主菜, 35 36); 3738this.foodList.Add( 39new FoodDto() 40 41ID = 3, 42Name = 水果沙拉
14、, 43Price = 58.00, 44Type = FoodType.甜品, 45 46); 4748this.foodList.Add( 49new FoodDto() 50 51ID = 4, 52Name = 奶油红菜汤, 53Price = 15.00, 54Type = FoodType.汤, 55 56); 5758this.foodList.Add( 59new FoodDto() 60 61ID = 5, 62Name = 杂拌汤, 63Price = 20.00, 64Type = FoodType.汤, 65 66); 67 6869/ 70/ 按照菜品名称获取菜品详细
15、信息 71/ 72/ 菜品名称 73/ 含有指定菜品信息的DTO 74public FoodDto GetFoodDetailByName(String foodName) 75 76foreach (FoodDto f in this.foodList) 77 78if (f.Name.Equals(foodName) 79 80return f; 81 82 8384return new FoodDto() ID = 0 ; 85 86 87第三步,通过View Interface规定View契约如果想实现Presenter和View的交互和无缝替换,必须在它们之间规定一个契约。一般来说,
16、每一张界面(注意是界面不是视图)都应该对应一个View接口,不过由于Demo只有一个页面,所以也只有一个View接口。 这里需要特别强调,View接口必须抽象于任何具体视图而服务于Presenter,所以,View接口中绝不能出现任何与具体视图相关的元素。例如,我们的Demo中是使用Windows Forms作为视图实现,但View接口中绝不可出现与Windows Forms相耦合的元素,如返回一个Winform的TextBox。因为如果这样做的话,使用其他技术实现的View就无法实现这个接口了,如使用Web Forms实现,而Web Forms是不可能返回一个Winform的TextBox的
17、。 下面给出视图接口的代码。 IMainView.cs:view source print?01using System; 02using System.Collections.Generic; 03using MVPSimple.Model; 0405namespace MVPSimple.Presenters 06 07/ 08/ MainView的接口,所有MainView必须实现此接口,此接口暴露给Presenter 09/ 10public interface IMainView 11 12/ 13/ View上的菜品名称 14/ 15String foodName get; set
18、; 1617/ 18/ View上点菜数量 19/ 20Int32 Amount get; set; 2122/ 23/ 判断某一菜品是否已经存在于点菜列表中 24/ 25/ 菜品名称 26/ 结果 27bool IsExistInList(String foodName); 2829/ 30/ 将某一菜品加入点菜列表 31/ 32/ 菜品DTO 33void AddFoodToList(FoodDto food); 3435/ 36/ 将某一已点菜品从列表中移除 37/ 38/ 欲移除的菜品名称 39void RemoveFoodFromList(String foodName); 4041
19、/ 42/ View显示提示信息给用户 43/ 44/ 信息内容 45void ShowMessage(String message); 4647/ 48/ View显示确认信息并返回结果 49/ 50/ 信息内容 51/ 用户回答是确定还是取消。True - 确定,False - 取消 52bool ShowConfirm(String message); 53 54 可以看到,IMainView抽象了如图3所示的界面,但又不包含任何与Windows Forms相耦合的元素,因此如果需要,以后完全可以使用Web Forms、WPF或SL等技术实现这个接口。第四步,实现Presenter 上文
20、说过,一个界面应该对应一个Presenter,这个Demo里只有一个界面,所以只有一个Presenter。Presenter仅于视图接口耦合,而并不和具体视图耦合,最好证据就是Presenter工程根本没有引用WinUI工程!代码如下: MainPresenter.cs:view source print?01using System; 02using System.Collections.Generic; 03using MVPSimple.Model; 0405namespace MVPSimple.Presenters 06 07/ 08/ MainView的Presenter 09/
21、10public class MainPresenter 11 12/ 13/ 当前关联View 14/ 15public IMainView View get; set; 1617/ 18/ 默认构造函数,初始化View 19/ 20/ MainView对象 21public MainPresenter(IMainView view) 22 23View = view; 24 2526#region Acitons 2728/ 29/ Action:将所点菜品增加到点菜列表 30/ 31public void AddFoodAction() 32 33if (String.IsNullOrE
22、mpty(View.foodName) 34 35View.ShowMessage(请选输入菜品名称); 36return; 37 38if (View.Amount = 0) 39 40View.ShowMessage(点菜的份数至少要是一份); 41return; 42 43if (View.IsExistInList(View.foodName) 44 45View.ShowMessage(String.Format(菜品【0】已经在您的菜单中, View.foodName); 46return; 47 4849FoodServices foodServ = new FoodServic
23、es(); 50FoodDto food = foodServ.GetFoodDetailByName(View.foodName); 51if (food.ID = 0) 52 53View.ShowMessage(String.Format(抱歉,本餐厅没有菜品【0】,View.foodName); 54return; 55 5657View.AddFoodToList(food); 58 5960/ 61/ Action:从点菜列表移除某一菜品 62/ 63/ 被移除菜品的名称 64public void RemoveFoodAction(String foodName) 65 66if
24、 (View.ShowConfirm(确定要删除吗?) 67 68View.RemoveFoodFromList(foodName); 69 70 7172#endregion 73 74第五步,实现View 这里我们使用Windows Forms实现View。如果朋友们有兴趣,完全可以自己试着用Web或WPF实现以下视图,同时可以验证P Logic的可复用性和视图无缝替换,亲身体验一下MVP模式的威力。Winform的View代码如下。 frmMain.cs:view source print?001using System; 002using System.Windows.Forms; 0
25、03using MVPSimple.Model; 004using MVPSimple.Presenters; 005006namespace MVPSimple.WinUI 007 008/ 009/ MainView的Windows Forms实现 010/ 011public partial class frmMain : Form, IMainView 012 013/ 014/ 相关联的Presenter 015/ 016private MainPresenter presenter; 017018/ 019/ 默认构造函数,初始化Presenter 020/ 021public f
26、rmMain() 022 023InitializeComponent(); 024this.presenter = new MainPresenter(this); 025 026027#region IMainView Members 028029/ 030/ View上的菜品名称 031/ 032public String foodName 033 034get return this.tbFoodName.Text; 035set this.tbFoodName.Text = value; 036 037038/ 039/ View上点菜数量 040/ 041public Int32
27、Amount 042 043get return (Int32)this.tbAmount.Value; 044set this.tbAmount.Value = (Decimal)value; 045 046047/ 048/ 判断某一菜品是否已经存在于点菜列表中 049/ 050/ 菜品名称 051/ 结果 052public bool IsExistInList(String foodName) 053 054foreach (ListViewItem i in this.lvFoods.Items) 055 056if (i.Text = foodName) 057 058return
28、 true; 059 060 061062return false; 063 064065/ 066/ 将某一菜品加入点菜列表 067/ 068/ 菜品DTO 069public void AddFoodToList(FoodDto food) 070 071ListViewItem item = new ListViewItem(); 072Double price = food.Price * (Double)this.tbAmount.Value; 073074item.Text = food.Name; 075item.SubItems.Add(food.Type.ToString()
29、; 076item.SubItems.Add(this.tbAmount.Value.ToString(); 077item.SubItems.Add(price.ToString(); 078this.lvFoods.Items.Add(item); 079 080081/ 082/ 将某一已点菜品从列表中移除 083/ 084/ 欲移除的菜品名称 085public void RemoveFoodFromList(String foodName) 086 087foreach (ListViewItem i in this.lvFoods.Items) 088 089if (i.Text = foodName) 090 091this.lvFoods.Items.Remove(i); 092 093 094 095096/ 097/ View显示提示信息给用户 098/ 099/ 信息内容 100public void ShowMessage(String message) 101 102MessageBox.Show(message, 信息, MessageBoxButtons.OK, MessageBoxIcon.Warning); 103 104105/ 106/ View显示确认信息并返回结果 10
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年一级注册建筑师之建筑结构题库练习试卷A卷附答案
- fob条款进口合同标准文本
- 基层应急能力建设指导意见
- 出售房屋合同样本
- 探索社团跨界合作计划
- 农药农膜化肥购销合同样本
- 南陵机组声屏障施工方案
- 个人食品委托加工合同样本
- 人防车位协议合同样本
- 借款换钱协议合同范例
- 妇女营养保健培训
- 时间序列的平稳性测试题及答案
- 2025-2030中国数据要素市场发展前景及趋势预测分析研究报告
- 陕西秦农银行招聘笔试真题2024
- 中外航海文化知到课后答案智慧树章节测试答案2025年春中国人民解放军海军大连舰艇学院
- 2025年华润燃气投资中国有限公司招聘笔试参考题库含答案解析
- (正式版)YBT 6328-2024 冶金工业建构筑物安全运维技术规范
- 2022年《跟徐老师学汉语》新HSK六级词汇词
- MDK5软件入门
- GB∕T 9441-2021 球墨铸铁金相检验
- 融资租赁单利名义利率、复利实际利率计算表(附公式版)
评论
0/150
提交评论