嘗試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上估計也不難,展示器管理那里要更改一下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM