MVP的意義
你是否遇到過這樣的問題:在開發時發現按鈕的button_onclick下的代碼雜亂無章,而又沒有其他的辦法(在以前有人設計應用層來解決這個問題),你需要驗證輸入,需要獲得控件的屬性值等着一些列的操作使得表現層變得那麼的臃腫,更不要說從winform移植到webfrom等不同的ui上面了。
怎麼解決這個問題,以前在用vb開發時需要多個應用層,來解決這個問題,但是總感覺效果不是很好。那麼怎麼解決這個問題呢?我們來仔細看看表現層,發現表現層包括兩個部分:user interface(UI)和presenter logic(P)。

MVP模型
也就是說我們UI應該僅僅局限于輸入輸出,至于展現邏輯其實我們平時沒有将其和UI分離,是以造成了展現層的臃腫。那麼MVP如何解決這個問題呢?我們先來看看MVP的模型結構圖:
可以看出我們通過将表現層進行分離來解決我們上面的問題。View通過interface(是對view的抽象)和Presenter進行通信它隻負責基本的輸入輸出 而Presenter負責界面邏輯 (如輸入是否為空),這樣一來不僅展現邏輯得到了分離而且它和view也進行了解耦使得view可以是任何類型的UI。
MVP的應用執行個體
說了那麼多,到底該怎麼去做呢?
我們來看一個具體的例子。假設我們現在在做一個winform的程式,需要做登入功能。現在我們已經布置好了界面,沒有寫任何的代碼。
我們盡量的簡化我們的應用,現在這個界面就是根據輸入的使用者名密碼到資料庫中比對如果使用者名和密碼都對就提示正确否則提示使用者名或密碼錯誤,當然在這裡我們還要驗證是否為空。
資料庫部分我們隻設定三個字段ID、UserName、UserPwd。
我們首先建立一個名為Presenter類庫項目根據界面 抽象出一個接口ILoginView:我們可以看出接口應該把汗兩個屬性UserName、UserPwd,兩個核心方法Login()、Exit(),當然為了使我們的接口更完整我們會有一個ShowMessage()的方法給出提示資訊。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Presenter
{
public interface ILoginView
{
string UserName
{
get;
set;
}
string UserPassword
{
get;
set;
}
void Login();
void Exit();
void ShowMessage(string msg);
}
}
然後我們再建立一個名為LoginPresenter的類來處理頁面邏輯,頁面邏輯不複雜無非就是登入和退出。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Presenter
{
public class LoginPresenter
{
private ILoginView view;
public LoginPresenter(ILoginView view)
{
this.view = view;
}
public void LoginAction()
{
if (view.UserName == string.Empty)
{
view.ShowMessage("使用者名不能為空!");
return;
}
if (view.UserPassword == string.Empty)
{
view.ShowMessage("密碼不能為空!");
return;
}
Model.UserService us = new Model.UserService();
if (us.IsBeing(view.UserName, view.UserPassword))
{
view.Login();
}
else
{
view.ShowMessage("使用者名或密碼錯誤!");
}
}
public void ExitAction()
{
view.Exit();
}
}
}
但是這裡它要利用Model中的服務,而且它是和具體的View無關的。
說了那麼多還沒有看到Model,建立一個名為Model的類庫項目,比較簡單,這裡貼出代碼.
User類:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace Model
{
/// <summary>
/// 實體類 (dbo.User)
/// 主要映射資料庫中相應的表
/// author:cmj
/// 2010/3/28 20:03:24
/// </summary>
public class User
{
public User(){}
public User
(
int ID ,
string UserName ,
string UserPassword
)
{
this.ID=ID;
this.UserName=UserName;
this.UserPassword=UserPassword;
}
public int ID
{
get;
set;
}
public string UserName
{
get;
set;
}
public string UserPassword
{
get;
set;
}
}
}
UserAccess類:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using Cmj.MyData;
namespace Model
{
/// <summary>
/// 實體操作類 (dbo.User).
/// 主要完成對資料庫的基本操作
/// author:cmj
/// 2010/3/28 20:03:24
/// </summary>
public class UserAccess:IUserAccess
{
#region 資料庫操作輔助類
DataOperate myDataOperate=new DataOperate();
#endregion
#region 向資料庫中插入一條新記錄。
/// <summary>
/// 向資料庫中插入一條新記錄。
/// </summary>
/// <param name="entity">實體類</param>
/// <returns>新插入記錄的編号</returns>
public void Insert(User entity)
{
//構造字插入符串(可以為Null的屬性在插入時可以不指派)
string _sql="insert into User(";
if(entity.UserName!=null)
{
_sql+="UserName ,";
}
if(entity.UserPassword!=null)
{
_sql+="UserPassword ,";
}
_sql=_sql.TrimEnd(',');
_sql+=") values(";
if(entity.UserName!=null)
{
_sql+="'"+entity.UserName+"' ,";
}
if(entity.UserPassword!=null)
{
_sql+="'"+entity.UserPassword+"' ,";
}
_sql=_sql.TrimEnd(',');
_sql+=")";
//執行插入
myDataOperate.executeCommand(_sql);
}
#endregion
#region 更新表User中指定的一條記錄
/// <summary>
/// 更新表User中指定的一條記錄
/// </summary>
/// <param name="entity">實體類</param>
public void Update(User entity)
{
//構造字插入符串(允許為NUll的值在修改時可以不給屬性指派)
string _sql="update User set ";
if(entity.UserName!=null)
{
_sql+="UserName='"+entity.UserName+"' ,";
}
if(entity.UserPassword!=null)
{
_sql+="UserPassword='"+entity.UserPassword+"' ,";
}
_sql=_sql.TrimEnd(',');
_sql+=" where ID="+entity.ID+"";
//執行插入
myDataOperate.executeCommand(_sql);
}
#endregion
#region 删除資料表User中的一條記錄
/// <summary>
/// 删除資料表User中的一條記錄
/// </summary>
/// <param name="ID">iD</param>
/// <returns>無</returns>
public void Delete(long id)
{
string _sql="delete from User where ID="+id+" ";
myDataOperate.executeCommand(_sql);
}
#endregion
#region 得到 User 記錄數目
/// <summary>
/// 得到 User 記錄數目
/// </summary>
/// <param>無</param>
/// <returns>記錄數目</returns>
public int GetCountOfAll()
{
string _sql="select count(*) as c from User";
int _count=int.Parse(myDataOperate.getDataTable(_sql).Rows[0]["c"].ToString());
return _count;
}
#endregion
#region 得到 User 資料實體
/// <summary>
/// 得到 User 資料實體
/// </summary>
/// <param name="id">實體id</param>
/// <returns>實體類</returns>
public User GetData(int id)
{
string _sql="select ";
_sql+="ID ,";
_sql+="UserName ,";
_sql+="UserPassword ";
_sql+=" from User where ID="+id+" ";
DataTable dt=myDataOperate.getDataTable(_sql);
User entity=new User();
entity.ID=(( dt.Rows[0]["ID"])==DBNull.Value)?0:Convert.ToInt32( dt.Rows[0]["ID"]);
if(dt.Rows[0]["UserName"].ToString()!=string.Empty)
{
entity.UserName= dt.Rows[0]["UserName"].ToString();
}
if(dt.Rows[0]["UserPassword"].ToString()!=string.Empty)
{
entity.UserPassword= dt.Rows[0]["UserPassword"].ToString();
}
return entity;
}
#endregion
#region 得到 User 中id最大的資料實體
/// <summary>
/// 得到 User 中id最大的資料實體
/// </summary>
/// <returns>實體類</returns>
public User GetMaxIDOfData()
{
string _sql="select ";
_sql+="ID ,";
_sql+="UserName ,";
_sql+="UserPassword ";
_sql+=" from User where ID=(select max(ID) from User) ";
DataTable dt=myDataOperate.getDataTable(_sql);
User entity=new User();
entity.ID=(( dt.Rows[0]["ID"])==DBNull.Value)?0:Convert.ToInt32( dt.Rows[0]["ID"]);
if(dt.Rows[0]["UserName"].ToString()!=string.Empty)
{
entity.UserName= dt.Rows[0]["UserName"].ToString();
}
if(dt.Rows[0]["UserPassword"].ToString()!=string.Empty)
{
entity.UserPassword= dt.Rows[0]["UserPassword"].ToString();
}
return entity;
}
#endregion
#region 得到資料表User指定的記錄
/// <summary>
/// 得到資料表User指定的記錄
/// </summary>
/// <returns>實體類集合</returns>
public IList< User> GetEligibleDataList(string sql)
{
IList< User> entities=new List< User>();
string _sql=sql;
DataTable dt=myDataOperate.getDataTable(_sql);
User entity;
foreach(DataRow dr in dt.Rows)
{
entity=new User();
entity.ID=(( dr["ID"])==DBNull.Value)?0:Convert.ToInt32( dr["ID"]);
if(dr["UserName"].ToString()!=string.Empty)
{
entity.UserName= dr["UserName"].ToString();
}
if(dr["UserPassword"].ToString()!=string.Empty)
{
entity.UserPassword= dr["UserPassword"].ToString();
}
entities.Add(entity);
}
return entities;
}
#endregion
#region 得到資料表User所有記錄
/// <summary>
/// 得到資料表User所有記錄
/// </summary>
/// <returns>實體類集合</returns>
public IList< User> GetDataList()
{
IList< User> entities=new List< User>();
string _sql="select * from User";
DataTable dt=myDataOperate.getDataTable(_sql);
User entity;
foreach(DataRow dr in dt.Rows)
{
entity=new User();
entity.ID=(( dr["ID"])==DBNull.Value)?0:Convert.ToInt32( dr["ID"]);
if(dr["UserName"].ToString()!=string.Empty)
{
entity.UserName= dr["UserName"].ToString();
}
if(dr["UserPassword"].ToString()!=string.Empty)
{
entity.UserPassword= dr["UserPassword"].ToString();
}
entities.Add(entity);
}
return entities;
}
#endregion
}
}
IUserAccess
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace Model
{
/// <summary>
/// 實體操作類接口 (dbo.User)
/// 主要用來規範實體行為
/// author:cmj
/// 2010/3/28 20:03:24
/// </summary>
public interface IUserAccess
{
void Insert(User entity);
void Update(User entity);
void Delete(long id);
int GetCountOfAll();
User GetData(int id);
User GetMaxIDOfData();
IList< User> GetEligibleDataList(string sql);
IList< User> GetDataList();
}
}
UserAccessFactory:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace Model
{
/// <summary>
/// dbo.User對應的資料工廠.
/// 主要是為了實作表示層和業務邏輯層的解耦
/// author:cmj
/// 2010/3/28 20:03:24
/// </summary>
public class UserAccessFactory
{
public static IUserAccess CreateUserAccess()
{
return new UserAccess();
}
}
}
當然上面僅僅完成實體對象的轉換以及基本中繼資料處理。下面肯定有業務邏輯嗎,這才是Model的重要部分,不過對于我們的例子,就太簡單了,看一下UserService:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Model
{
public class UserService
{
public bool IsBeing(string name,string password)
{
UserAccess uAccess = UserAccessFactory.CreateUserAccess() as UserAccess;
IList<User> users= uAccess.GetEligibleDataList("select * from [User] where UserName='"+name+"' and UserPassword='"+password+"' ");
if (users.Count == 1)
{
return true;
}
else
{
return false;
}
}
}
}
以上是整個就是Model的代碼,看起來有點多其實很簡單,為了讓大家更好的了解MVP我沒有在這部分省略代碼。
好了,Model固然重要但是接下來的就更重要了,因為我們的問題還沒有解決。
我們建立一個winform應用程式有兩個form一個是LoginView另一個是MainView,在LoginView中實作ILoginview接口執行相應的操作。using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MVP
{
public partial class LoginView : Form,Presenter.ILoginView
{
Presenter.LoginPresenter loginPresenter;
public LoginView()
{
InitializeComponent();
}
#region ILoginView 成員
public string UserName
{
get
{
return tbUserName.Text;
}
set
{
tbUserName.Text = value;
}
}
public string UserPassword
{
get
{
return tbPassword.Text;
}
set
{
tbPassword.Text = value;
}
}
public void Login()
{
MainView mv = new MainView();
mv.Show();
}
public void Exit()
{
Application.Exit();
}
public void ShowMessage(string msg)
{
MessageBox.Show(msg);
}
#endregion
private void LoginView_Load(object sender, EventArgs e)
{
loginPresenter = new Presenter.LoginPresenter(this);
}
private void btnLogin_Click(object sender, EventArgs e)
{
loginPresenter.LoginAction();
}
private void btnCancel_Click(object sender, EventArgs e)
{
loginPresenter.ExitAction();
}
}
}
好了,到此為止我們的功能按照預期的實作了。代碼很整潔,而且即便要修改成b/s結構我們也很容易,整個presenter都不用動。
注意我們是隻寫了一個view接口和一個presenter,那是因為我們隻處理了一個視窗的業務,事實上是一個視窗對于一個view接口和一個presenter。
例子下載下傳:
MVP.rar