尝试MVP模式
尝试MVP模式
对MVP模式的接触,是我偶然一次在百度上搜MVC的时候开始,当时对MVC都不了解,甭说MVP了。后来MVC弄懂了,现在就来了解一下MVP。
MVP 是从经典的模式MVC演变而来的,难怪看那个结构图有点相像。
MVC模式的结构图,M,V,C各代表什么不说了
MVP模式的结构图,M和V的含义跟MVC中的结构一样,区别的就是C(Controller)和P(Presenter)。感觉这个区别就导致了模式产生性质的变化。至少从几何角度来看,由一个稳定的三角型变成一条直线。在MVC中即使在Controller对View和Model的控制之下,View和Model之间仍然有联系,至少View上控件绑定的数据是与Model的某个字段有关的。不过在MVP中Presenter则把原本MVC中View与Model的联系砍断了,View上面那个控件绑定什么数据它本身不知道,Presenter才知道。这样View只是负责呈现部分,使得它的职责更单一了。再者Presenter不是调用View本身,而是调用一个由View实现的接口,这样使得View与Presenter的联系更松散了。这么说来,整个MVP模式中的成员一共有四个
View(视图):实现IVew接口,负责界面呈现。 IView(视图接口):提供一些方法,属性供展示器调用获取,从而得知视图的状态或某些信息或对视图进行某些操作,同时也外放了一些方法供展示器注册,使得视图能在需要的时刻对展示器发出某些请求。 Presenter(展示器):整个MVP模式的核心,负责对视图的操作,数据的绑定,必要时响应来自视图的请求,在有需要的时候会借助模型完成一些业务。 Model(模型):完成整个模式中必要的业务逻辑。浏览了一些园友的博文后,我也尝试实现了一个MVP模式。项目的结构如下图。
从上图可以很明显的看出MVP的三部分,另外Common目录下存放的主要是MVP模式里的一些基类,接口等等,本项目还使用了一个轻量级的Ioc框架Ninject,为了尽量改动Common里的类,使用Ninject时要绑定的接口是实现类以配置的形式来实现,配置的信息就存放在BindingConfig.xml文件里面。
看一下Common里面包含的类
IocContainer.cs
Ioc的容器
IView.cs
视图接口的基接口
MyEventArgs.cs
扩展了事件和委托的参数
PresenterBase.cs
所有展示器的基类
PresenterManager.cs
通过展示器展示其视图
WinFormInjectModule .cs
Ioc的接口与实现类的绑定
由于对Ninject还不是很熟悉,对它的用法解释不了太多
IocContainer的定义如下
1 public class IocContainer
2 {
3 private static IKernel _kernel;
4
5 public static IKernel Container
6 {
7 get
8 {
9 if (_kernel == null )
10 _kernel = new StandardKernel( new WinFormInjectModule());
11 return _kernel;
12 }
13 }
14 }
这里用到了WinFormInjectModule类,它继承了NinjectModule,里面就重写了Load方法实现绑定,由于这里的绑定时通过配置实现的,所以这里还涉及到读取和分析配置信息
1 public class WinFormInjectModule : Ninject.Modules.NinjectModule
2 {
3 public override void Load()
4 {
5
6 List<Tuple< string , string >> bindingList = GetBindingConfig();
7 Type bindType,toType;
8 foreach (Tuple< string , string > item in bindingList)
9 {
10 bindType= Type.GetType(item.Item1);
11 if (item.Item2.Length == 0 )
12 {
13 Bind(bindType).ToSelf();
14 continue ;
15 }
16 toType = Type.GetType(item.Item2);
17 Bind(bindType).To(toType);
18 }
19 }
20
21 private List<Tuple< string , string >> GetBindingConfig()
22 {
23 List<Tuple< string , string >> result = new List<Tuple< string , string >> ();
24 XmlDocument xmlDoc = new XmlDocument();
25 if (!File.Exists( " BindingConfig.xml " )) throw new IOException( " BindingConfig.xml 不存在 " );
26 xmlDoc.Load( " BindingConfig.xml " );
27 XmlNodeList nodelist = xmlDoc.SelectNodes( " //BindingSetting/Binding " );
28 string bind,to;
29 foreach (XmlNode node in nodelist)
30 {
31 bind= string .Empty;
32 to= string .Empty;
33 bind = node.Attributes[ " bind " ].Value;
34 if (node.Attributes[ " to " ] != null ) to = node.Attributes[ " to " ].Value;
35 result.Add( new Tuple< string , string > (bind,to));
36 }
37 return result;
38 }
39 }
配置的定义如下
1 < BindingSetting > 2 < Binding bind ="TestMVP.Model.IUser" to ="TestMVP.Model.UserModel" /> 3 < Binding bind ="TestMVP.View.ILoginView" to ="TestMVP.View.LoginView" /> 4 < Binding bind ="TestMVP.Presenter.LoginPresenter" /> 5 </ BindingSetting >
bind属性就是要绑定的类或者接口,to就是绑定到的类,如果只是绑定自己的话就在bind属性填类名则可,to不用填了。
展示器的基类定义如下
1 public class PresenterBase<T> where T : IView
2 {
3 private T _view;
4
5 public PresenterBase(T view)
6 {
7 this .View = view;
8 }
9
10 public T View
11 {
12 get { return _view; }
13 set { _view = value; }
14 }
15 }
以接口的形式对视图进行访问的话,就可以避免直接访问视图的实例,减少了对视图的依赖。
考虑到在展示器里打开别的展示器管理的视图时,原本可以构造一个展示器实例,然后获取其视图进行展示,可是在一个展示器里构造另一个展示器,这样的做法好像不妥,于是定义了一个类专门用于打开别的视图用的。
当要打开某个视图(也就是窗体)时,就可以调用PresenterManager的静态方法
1 public class PresenterManager
2 {
3 public static void ShowView( string presenterName,FormAction formAction)
4 {
5 Type type = Type.GetType( " TestMVP.Presenter. " + presenterName);
6 object p = Common.IocContainer.Container.GetService(type);
7 System.Windows.Forms.Form frm = type.GetProperty( " View " ).GetValue(p, null ) as System.Windows.Forms.Form;
8 switch (formAction)
9 {
10 case FormAction.Run: System.Windows.Forms.Application.Run(frm);
11 break ;
12 case FormAction.Show: frm.Show();
13 break ;
14 case FormAction.ShowDialog: frm.ShowDialog();
15 break ;
16 default :
17 break ;
18 }
19 }
20
21 public static void ShowView( string presenterName)
22 {
23 ShowView(presenterName, FormAction.Show);
24 }
25 }
26
27 public enum FormAction { Run,Show,ShowDialog }
下面则做一个简单的Demo,是登录功能的
首先是模型的,先定义了一个IUser接口,届时展示器想调用模型的方法是就通过这个接口来调用,免除了对模型其他成员的访问
1 public interface IUser
2 {
3 bool CheckLogin( string user, string password);
4 }
再由一个IModelUser实现这个接口
1 public class UserModel:IUser
2 {
3 public bool CheckLogin( string user, string password)
4 {
5 if (user == " admin " && password == " 123456 " )
6 return true ;
7 return false ;
8 }
9 }
接着到展示器
1 public class LoginPresenter:PresenterBase<ILoginView>
2 {
3 [Inject]
4 public IUser UserModel { set ; get ; }
5
6 public LoginPresenter(ILoginView view): base (view)
7 {
8 this .View = view;
9 this .View.OnLogin += new MyEventHandler(View_OnLogin);
10 }
11
12 void View_OnLogin( object sender, MyEventArgs e)
13 {
14 bool result = UserModel.CheckLogin( this .View.IDBoxText, this .View.PasswordBoxText);
15 e.OptionResult= result;
16 if (result)
17 {
18 PresenterManager.ShowView( " SystemPresenter " );
19 (sender as Form).Hide();
20 }
21 }
22 }
在构造展示器实例时,给视图的事件绑定一个方法,相应登录视图的登录验证请求,在改方法内调用模型的方法验证用户名密码,把结果通过委托的参数传递给视图。如果验证通过了就隐藏登录视图,显示主界面。
最后到视图
1 public interface ILoginView:IView
2 {
3 event MyEventHandler OnLogin;
4 string IDBoxText { get ; set ; }
5
6 string PasswordBoxText { get ; set ; }
7 }
8
9 public partial class LoginView : Form,ILoginView
10 {
11 public LoginView()
12 {
13 InitializeComponent();
14 }
15
16 private void button1_Click( object sender, EventArgs e)
17 {
18 MyEventArgs args= new MyEventArgs();
19 if (OnLogin != null ) OnLogin( this , args);
20 if (! args.OptionResult)
21 MessageBox.Show( " Fail " );
22 }
23
24 public event MyEventHandler OnLogin;
25
26 public string PasswordBoxText
27 {
28 get { return this .tbPw.Text; }
29 set { this .tbPw.Text = value; }
30 }
31
32 public string IDBoxText
33 {
34 get { return this .tbID.Text; }
35 set { tbID.Text = value; }
36 }
37 }
视图这里ILoginView是继承了IView接口,里面声明了登录视图应该外放的事件和属性,那登录界面来说
虽然很明显看得出ID后的输入框的值是用户ID,Password后面的输入框的值是用户密码,但是这些对于一个视图来说都是不知其含义的,知道含义的是展示器,视图只是把值外放出去给展示器获取。正如一位园友说的,视图就该尽量吧控件多外放出去。不过我觉得某些简单的界面逻辑还是放在视图上比较好,例如单击了某个按钮使得另一个输入框变灰之类的。
这样就牵强地使用了一下MVP模式,有位园友在讨论MVC时说过,没发挥到MVC的优势时干脆用回以前的WebForm,MVP也一样吧,期待能真正用上它的时候。由于最近都是从事C/S的开发,对C/S比较熟悉,做的这个小尝试也是用WinForm的,但转到WebForm上估计也不难,展示器管理那里要更改一下。
分类: C# , 设计模式
标签: MVP
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息