MVP的PV模式与SC模式
MVC是现今挺被推崇的一种架构模式,而MVP在MVC的基础对视图与模型上再解耦,使结构和各自的功能也有所改变。在去年鄙人也尝试了一下使用MVP做了一个小Demo,作为了MVC的一个变体,MVP还分了两种模式,这个之前是不了解的,直到看了蒋老师的著作才知道。
在MVP里面重点还是看View与Presenter之间的交互,View可以直接去调用Presenter,但Presenter对View的调用不是直接去访问View的实例,而是通过由View去实现的一个IView接口。唉MVP中能看到的就是这种对象与对象之间的交互,在细化一点去看,处理UI的逻辑部分搁在View里面还是搁在Presenter里面。这就是PV(Passive View被动视图)模式和(Supervising Controller监督控制器)模式的区别了。那么先来对两种模式稍作介绍,然后再通过一个Demo再体验一下两种模式。
PV模式
在PV模式中,视图上的所有View的数据(具体点来说可以是控件的属性)都会暴露出来,供Presenter来调用。View的任务就是简单地在需要的时候把数据呈现出来,或者把数据提供给Presenter。涉及到一些UI逻辑的,无论复杂与否都与View本身无关。UI逻辑是Presenter的任务,这种方式使得View以一种很轻量级的形式存在,UI逻辑放到了Presenter里面,这样的做法有一种好处,就是方便了对UI逻辑做测试。但不足之处往往在于为了让Presenter对View数据操作更粒度更细化,View会提供比较多的属性,这样会使得IView接口变得庞大起来。
SC模式
在SC模式中鄙人感觉最为受到强调的就是数据流动的单向性。无论是Presenter把数据推送到View这个方向,还是View接收到界面操作然后提交到Presenter,都应该是要单向的。也就是说当Prensenter把消息推送到View之后,View不要再给Presenter反馈信息(返回值),View把界面操作的响应提交到Presenter之后也不需要Presenter回传点什么。
那么下面就看看实际的例子
这是一个简单的通讯录,点击左边联系人名单,在右边就会显示出联系人的姓名和电话
对于View的布局而言,两者都是一样的
设计界面 | 运行效果 |
还有另一个相同的就是模型Model,这里的模型只有两个属性,一个是姓名,另一个是电话号码,还提供了一个静态方法去创建一个联系人的列表,代码定义如下
1 public class ContactModel 2 { 3 public string PersionName { get; set; } 4 5 public string PhoneNumber { get; set; } 6 7 public static List<ContactModel> GetContactModelLists() 8 { 9 List<ContactModel> result = new List<ContactModel>(); 10 11 result.Add(new ContactModel() { PersionName="Tom",PhoneNumber= "12345678901" }); 12 result.Add(new ContactModel() { PersionName="HopeGi",PhoneNumber="13450275638" }); 13 result.Add(new ContactModel() { PersionName="Jack",PhoneNumber="13540243844" }); 14 result.Add(new ContactModel() { PersionName="Tim" }); 15 16 return result; 17 } 18 }
两种模式的差别在于IView和Presenter
PV模式的视图只在提供界面的状态的属性,故IView的接口都是定义属性的
1 interface IContactView 2 { 3 IEnumerable<ContactModel> ListBoxDataSource { set; } 4 5 ContactModel SelectedContact { get; } 6 7 string DetailPersonName { set; } 8 9 string DetailPhoneNumber { set; } 10 11 event Action SelectedContactChanged; 12 }
实现IVew接口的Form只需要把传入的属性赋到相应的控件上或者从相应的控件上获取属性返回回去则可。
1 public partial class Form1 : Form,IContactView 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 public event Action SelectedContactChanged; 9 10 public IEnumerable<ContactModel> ListBoxDataSource 11 { 12 set { 13 this.lstBoxContact.DataSource = value; 14 this.lstBoxContact.DisplayMember = "PersionName"; 15 } 16 } 17 18 public ContactModel SelectedContact 19 { 20 get 21 { 22 return this.lstBoxContact.SelectedItem as ContactModel; 23 } 24 } 25 26 public string DetailPersonName 27 { 28 set { this.lbDetailPersonName.Text = value; } 29 } 30 31 public string DetailPhoneNumber 32 { 33 set { this.lbDetailPhoneNumber.Text = value; } 34 } 35 36 private void lstBoxContact_SelectedIndexChanged(object sender, EventArgs e) 37 { 38 if (SelectedContactChanged != null) SelectedContactChanged(); 39 } 40 }
在视图中属性的get/set操作只是简单的取值赋值,那么判断逻辑与控制控制就交给了Presenter去处理
1 class ContactPresenter 2 { 3 IContactView view; 4 ContactModel model; 5 6 public ContactPresenter(IContactView view) 7 { 8 this.view = view; 9 view.SelectedContactChanged += new Action(view_SelectedContactChanged); 10 } 11 12 void view_SelectedContactChanged() 13 { 14 ContactModel _model = view.SelectedContact; 15 if (_model == null) return; 16 view.DetailPersonName =string.IsNullOrEmpty(_model.PersionName)?"--": _model.PersionName; 17 view.DetailPhoneNumber =string.IsNullOrEmpty(_model.PhoneNumber)?"--": _model.PhoneNumber; 18 } 19 20 public void ShowView() 21 { 22 if (view != null && view is Form) 23 { 24 view.ListBoxDataSource = ContactModel.GetContactModelLists(); 25 (view as Form).ShowDialog(); 26 } 27 } 28 }
那么现在看看另一种的模式——SC模式
主要还是要看IVew的定义,因为IVew能提供什么,Presenter就能调用什么,至于怎么调用那是其次。
1 interface IContactView 2 { 3 void BindListBoxDataSource(IEnumerable<ContactModel> datas); 4 void BindContactDetail(ContactModel modelDetail); 5 event Action<ContactModel> SelectedContactChanged; 6 }
一看上去其实我觉得跟PV模式的接口没太大区别的,因为如果转到Java而言,属性的get/set操作也是通过方法来实现的,这样子只是更换了方法名而已,但是再细心对比一下可以发现有差别,set操作的确换成了用方法来实现而不是用属性,但是这些方法都没有返回值,方法中也没有get操作,因为一般Presenter如果要获取View的参数是,基本上是界面上有用户操作触发了事件,这样Presenter处理用户事件时所需要的数据都用事件参数一并传到Presenter里面,待到Presenter处理完毕后把需要数据展示到View中去。
1 class ContactPresenter 2 { 3 IContactView view; 4 ContactModel model; 5 6 public ContactPresenter(IContactView view) 7 { 8 this.view = view; 9 this.view.SelectedContactChanged += new Action<ContactModel>(view_SelectedContactChanged); 10 } 11 12 void view_SelectedContactChanged(ContactModel obj) 13 { 14 this.view.BindContactDetail(obj); 15 } 16 17 public void ShowView() 18 { 19 if (view != null && view is Form) 20 { 21 this.view.BindListBoxDataSource(ContactModel.GetContactModelLists()); 22 (view as Form).ShowDialog(); 23 } 24 } 25 }
这里的Presenter比PV的要简单,因为有一部分代码转移到View中去了
1 public partial class Form1 : Form,IContactView 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 private void lstBoxContact_SelectedIndexChanged(object sender, EventArgs e) 9 { 10 if (SelectedContactChanged != null) SelectedContactChanged(lstBoxContact.SelectedItem as ContactModel); 11 } 12 13 public void BindListBoxDataSource(IEnumerable<ContactModel> datas) 14 { 15 if (datas == null) return; 16 lstBoxContact.DataSource = datas; 17 lstBoxContact.DisplayMember = "PersionName"; 18 } 19 20 public void BindContactDetail(ContactModel modelDetail) 21 { 22 if (modelDetail == null) 23 { 24 lbDetailPersonName.Text = "--"; 25 lbDetailPhoneNumber.Text = "--"; 26 } 27 else if (string.IsNullOrEmpty(modelDetail.PersionName)) 28 lbDetailPersonName.Text = "--"; 29 else if (string.IsNullOrEmpty(modelDetail.PhoneNumber)) 30 lbDetailPhoneNumber.Text = "--"; 31 else 32 { 33 lbDetailPersonName.Text=modelDetail.PersionName; 34 lbDetailPhoneNumber.Text = modelDetail.PhoneNumber; 35 } 36 } 37 38 public event Action<ContactModel> SelectedContactChanged; 39 }
上述两个例子也模仿了蒋老师书中的Demo,但个人觉得这两个例子也不完全是Model,大概是我用的Model和是把存储数据的Model和处理一些逻辑的Model揉在一起了,因为在ASP.NET MVC中的Model与存储数据用的实体类是有区别的。对SC与PV的理解仅参考了蒋老师书上的一点内容,网上内容不少,如果上面有什么说错的欢迎大家指正,如果大家有更多的参考资料也欢迎分享,谢谢!