好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

尝试MVP模式

尝试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://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于尝试MVP模式的详细内容...

  阅读:37次