MVP模式应用

MVP的意义

你是否遇到过这样的问题:在开发时发现按钮的button_onclick下的代码杂乱无章,而又没有其他的办法(在以前有人设计应用层来解决这个问题),你需要验证输入,需要获得控件的属性值等着一些列的操作使得表现层变得那么的臃肿,更不要说从winform移植到webfrom等不同的ui上面了。

怎么解决这个问题,以前在用vb开发时需要多个应用层,来解决这个问题,但是总感觉效果不是很好。那么怎么解决这个问题呢?我们来仔细看看表现层,发现表现层包括两个部分:user interface(UI)和presenter logic(P)。

MVP模式应用

MVP模型

也就是说我们UI应该仅仅局限于输入输出,至于展现逻辑其实我们平时没有将其和UI分离,因此造成了展现层的臃肿。那么MVP如何解决这个问题呢?我们先来看看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