• 尝试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调为基准)
    小程序页面跳转问题
    Adobe-premierej剪辑视频分辨率问题及格式转换画面质量分辨率
    关于ps抠图问题,制作公章,公章的复制
    premiere视频剪辑关于导出视屏分辨率的问题
    最后一个单词的长度
    10.两个链表的交叉
    软件工程——个人总结
    软件工程——团队专业4
    软件工程——团队答辩
  • 原文地址:https://www.cnblogs.com/HopeGi/p/3055144.html
Copyright © 2020-2023  润新知