设计模式学习:Model View Presenter (MVP)
设计模式学习:Model View Presenter (MVP)
经常看见圆子里有人谈论 mvp mvc 的相关技术知识,一直也没怎么练习
今天正好有人管我要一个demo
需求是这样的:
利用mvp模式做个 AS.NET 和 Windows Forms 都可以运行的Demo
抽空做了一下,也不知道我个人理解的是否正确、反正demo 已经做了
以我个人的理解,MVP主要就是要实现 界面和后台业务的完全隔离。
把原来 View 要做的事情的都交给 Presenter 尽量做到最大化的分离
先看运行效果
效果差不多的;什么样式也没加丑陋了点呵呵。
业务很简单,就是加载时显示全部数据,文本框输入数据在【查询】又刷新数据
在看一下解决方案视图
13个项目,呵呵不要害怕没多少代码的;
解决方案说明:
这里空间都省略了要不子太长了也懒得改了
[V] 就是 View 了
IView - 视图接口
[WebView]
WebView - Web下实现视图接口
ViewSite - 网站了
[WinView]
WinView - Win下实现视图接口
WinApp - WinForms 程序
[P]
Presenter 就一个
[M] 基本和 PetShop 是一样的我就不多解释了
[公用]
ServerUtility - 里面就一个“决策类”这里和 PetShop 不一样用这个代替工厂那个层
[数据处理实现] 实现 IDAL 的项目
TestDAL - 测试项目,就是随机生成些数据
WebServiceDAL - 一个使用 WebService 的 DAL
ShareData 数据实体
IDAL
BILL
[其他]
TestWebService 一个 WebService,WebServiceDAL 用的;
在来看看项目引用的关系
上图虚线部分是,动态引用(工程里没引用),图不是什么标准图,就是描述一下各个项目之间的引用关系;
ShareData 引用的地方过多,没有画上省略了。
TestWebService 不属于模式的一部分也省略了。
代码说明部分(不重要的代码我就不讲了,有需要的在问吧)
1) View 部分
WebView、WinView 分别实现了 IView 下面那一些代码进行对比讲解
先说说 OpenSource.FlashElf.MVP.IView
IUIDefault - 程序主视图接口,继承 IView
IView - 继承 IUIControl
IUIControl - 留着以后扩展
--抽象了3个控件,都继承 IUIControl
Controls.IUIButton - 按钮控件
Controls.IUIDataBindControl - 数据绑定控件
Controls.IUITextControl - 文本框控件
为什么要这么封装那? 因为要做到最大化分离视图先看代码吧
【OpenSource.FlashElf.MVP.WebView.Controls.UIDataBindControl】
public class UIDataBindControl : IUIDataBindControl
{
CompositeDataBoundControl _control;
public UIDataBindControl(CompositeDataBoundControl control)
{
_control=control;
}
#region IDataBindControl 成员
public void DataBind(object data)
{
_control.DataSource = data;
_control.DataBind();
}
#endregion
}
【OpenSource.FlashElf.MVP.WinView.Controls.UIDataBindControl】
{
const string PropertyDataSource = "DataSource";
Control _control;
SetDataSource _setDataSource = null;
//DataGrid 和 DataGrid View 也没有个统一的接口,还要两个构造函数
public UIDataBindControl(DataGrid control)
{
_control = control;
_setDataSource = SetPropertyToDelegate<SetDataSource>(control, PropertyDataSource);
}
public UIDataBindControl(DataGridView control)
{
_control = control;
_setDataSource = SetPropertyToDelegate<SetDataSource>(control, PropertyDataSource);
}
#region IDataBindControl 成员
public void DataBind(object data)
{
_setDataSource(data);
}
#endregion
/// <summary>
/// [!]暂时放在这里
/// 通用函数,把set属性转换为委托,以提高性能
/// 没办法 DataGrid,DataGridView 也没有个通用的接口
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control"></param>
/// <param name="prname"></param>
/// <returns></returns>
private static T SetPropertyToDelegate<T>(Control control, string prname) where T : class
{
Type t = control.GetType();
PropertyInfo pinfo = t.GetProperty(prname);
MethodInfo method = pinfo.GetSetMethod();
Delegate dec = Delegate.CreateDelegate(typeof(T), (object)control, method);
return dec as T;
}
}
//[!]暂时放在这里,等有在用到的时候在重构
delegate void SetDataSource(object o);
看完以上两个类的实现后,各位看官可能了解的我为何还要封装 WebView、WinView 这两层了
因为 winForm 和 ASP.net 控件、差异太大了,要做通用的只有这样了,否则 Presenter 就没法玩了
如果是单纯 Web 或 WinForm 大可不必这么郁闷;
Web 的不是代码少是还没写完懒得写了有些、DataGrid 和 GridView 看似都有 DataSource 不过又是
不是一个基类,又不是一个接口,郁闷
SetPropertyToDelegate 函数可以看看,是把属性转换为委托的
在使用次数比较多的时候、比直接反射快很多
专门对付函数或属性相同但不是一个基类接口的情况
其他那些 IView 接口的实现也都是差不多的;
【应用程序的代码】
【OpenSource.FlashElf.MVP.WinApp】 - WinForm 程序
{
public partial class Form1 : Form
{
DefaultPresenter _presenter;
public Form1()
{
InitializeComponent();
_presenter = new DefaultPresenter(new UIDefault(this, this.btnQuery,
this.gvList, this.txtQuery));
}
}
}
【ViewSite】 - 网站
{
public _Default()
{
}
DefaultPresenter _presenter;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
_presenter = new DefaultPresenter( new UIDefault(this,this.btnQuery
,this.gvList,this.txtQuery));
}
}
就是调用了一下 Presenter
Web Win 几乎一摸一样;就是 UIDefault 的名称空间不同
有人会问你怎么没实现 IView 项目里的接口?
这个问题......哎呀,MVP 没说一定要实现个什么接口吧
在说程序窗口上不是越少代码越好吗?外挂的就不算了吗?
我这里 UIDefault 就是 View 实现了 IView 里的接口了已经;
看一下 UIDefault 怎么实现的吧
{
public class UIDefault : IUIDefault
{
Form _page;
UIDataBindControl _gridList;
UIButton _btnQuery;
UITextControl _txtBoxQuery;
public UIDefault(Form page, Button btn, DataGridView grid, TextBox txtBox)
{
this._page = page;
this._page.Load +=new EventHandler(_page_Load);
_gridList = new UIDataBindControl(grid);
_btnQuery = new UIButton(btn);
_txtBoxQuery = new UITextControl(txtBox);
}
void _page_Load(object sender, EventArgs e)
{
if(Load !=null)
Load(sender,e);
}
#region IUIDefault 成员
public IUIDataBindControl GridList
{
get { return _gridList; }
}
public IUIButton BtnGetData
{
get { return _btnQuery; }
}
public IUITextControl TextBoxQuery
{
get { return _txtBoxQuery; }
}
#endregion
#region IView 成员
public event EventHandler Load;
#endregion
}
}
namespace OpenSource.FlashElf.MVP.WebView
{
public class UIDefault : IUIDefault
{
public Page _page;
public UIDataBindControl _gridList;
public UIButton _btnQuery;
public UITextControl _txtBoxQuery;
public UIDefault(Page page,Button btn,GridView grid,TextBox txtBox)
{
this._page = page;
this._page.Load+=new EventHandler(_page_Load);
_gridList = new UIDataBindControl(grid);
_btnQuery = new UIButton(btn);
_txtBoxQuery = new UITextControl(txtBox);
}
void _page_Load(object sender, EventArgs e)
{
if(!_page.IsPostBack)
{
if(Load !=null)
Load(sender,e);
}
}
#region IUIDefault 成员
public IUIDataBindControl GridList
{
get { return _gridList; }
}
public IUIButton BtnGetData
{
get { return _btnQuery; }
}
public IUITextControl TextBoxQuery
{
get { return _txtBoxQuery; }
}
#endregion
#region IView 成员
public event EventHandler Load;
#endregion
}
}
做了一点点的处理,代码几乎是一样的、就是load 函数有些不同
感谢ms web win 都有 Load 事件都是 EventHandler 要不
就没法玩啦又要做特殊处理;
2) Presenter 部分
OpenSource.FlashElf.MVP.Presenter.DefaultPresenter
using System.Collections.Generic;
using System.Text;
using OpenSource.FlashElf.MVP.IView;
using System.Data;
using OpenSource.FlashElf.MVP.Model.ShareData;
using OpenSource.FlashElf.MVP.Model.BILL.Manager;
namespace OpenSource.FlashElf.MVP.Presenter
{
public class DefaultPresenter
{
IUIDefault _view;
public DefaultPresenter(IUIDefault view)
{
_view = view;
_view.Load += new EventHandler(_view_Load);
_view.BtnGetData.Click += new EventHandler(BtnGetData_Click);
}
void BtnGetData_Click(object sender, EventArgs e)
{
UserInfos datas = new UserInfos();
if(_view.TextBoxQuery.Text.Trim().Length!=0)
{
//掉用 BILL
UserInfo data = UserManager.GetUserInfoByLoginName( _view.TextBoxQuery.Text );
datas.Add(data);
_view.GridList.DataBind(datas);
}
else
{
_view_Load(null,e);
}
}
void _view_Load(object sender, EventArgs e)
{
//掉用 BILL
UserInfos datas = UserManager.GetUserInfoALL();
_view.GridList.DataBind(datas);
}
}
}
终于可以到 Presenter 很失望也没多少代码?
demo 吗就3个控件所以能有多少代码
有人是不是要问 BILL 为啥都是静态的、谁说 BILL 不可以是静态的?
我是没看那个设计模式里写过 业务处理层,一定不能是静态的。
用什么方式要根据业务定吧?
3) 看看 Model
Model 这个词到底应该放在那里 PetShop 里明明就是个实体类
哎...,不过mvp 里却是整个的后台处理,郁闷“鸟”文真难理解啊...
先看BILL
using System.Collections.Generic;
using System.Text;
using OpenSource.FlashElf.MVP.Model.ShareData;
using OpenSource.FlashElf.MVP.ServerUtility;
using OpenSource.FlashElf.MVP.Model.IDAL.Manager;
namespace OpenSource.FlashElf.MVP.Model.BILL.Manager
{
public class UserManager
{
static readonly IUserManager _DAL;
static UserManager()
{
//创建 IDAL
_DAL = DecisionSimpleFactoryHelper.Create<IUserManager>("UserManagerDAL", "Manager");
}
public static UserInfos GetUserInfoALL()
{
UserInfos models = _DAL.GetUserInfoALL();
return models;
}
public static UserInfo GetUserInfoByLoginName(string name)
{
UserInfo model = null;
if (string.Concat(name).Trim().Length > 0 )
{
model = _DAL.GetUserInfoByLoginName(name);
}
else
{
throw new ArgumentNullException("Name");
}
return model;
}
}
}
估计有人又会说,IUserManager 为啥也是静态的,一点也不oo?
呵呵、省内存啊(不信自己试验)对于那种DAL层数据库用完就 close 的、而且没什么实例变量的
应用比较适合
而且 Web 你覆盖 dll 就会重新启动应用,不会存在什么问题
我好几个应用都这么干的
DecisionSimpleFactoryHelper.Create 是啥?
用他把,PetShop 那个工厂项目给省略了,我没感觉有啥不好的反正
要用才是硬道理,而且这也不违反设计模式吧;
代码
{
/// <summary>
/// 简单工厂决策类
/// </summary>
public class DecisionSimpleFactoryHelper
{
const string Key = "SimpleFactoryConfig";
/// <summary>
/// 从配置文件反射加载类
/// </summary>
/// <param name="AppSettingsKey">配置文件的 key</param>
/// <param name="className">类名</param>
/// <param name="subNamespace">名称空间</param>
/// <returns>反射的对象</returns>
public static object Create(string AppSettingsKey, string className, string subNamespace)
{
string path = ConfigurationManager.AppSettings[AppSettingsKey];
string classFillName;
if (subNamespace != null && subNamespace.Length > 0)
classFillName = string.Concat(path, ".", subNamespace, ".", className);
else
classFillName = string.Concat(path, ".", className);
return Assembly.Load(path).CreateInstance(classFillName);
}
/// <summary>
/// 读取 Key 下的 dal 类
/// </summary>
/// <param name="className"></param>
/// <param name="subNamespace"></param>
/// <returns></returns>
public static object Create(string className, string subNamespace)
{
return Create(DecisionSimpleFactoryHelper.Key, className, subNamespace);
}
/// <summary>
///
/// </summary>
/// <param name="className"></param>
/// <returns></returns>
static object Create(string className)
{
return Create(className, null);
}
/// <summary>
/// 范型实现
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="className"></param>
/// <param name="subNamespace"></param>
/// <returns></returns>
public static T Create<T>(string className, string subNamespace)
{
object o = Create(className, subNamespace);
return (T)o;
}
/// <summary>
/// 范型实现
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="className"></param>
/// <returns></returns>
public static T Create<T>(string className)
{
return Create<T>(className, null);
}
}
}
看看配置文件
Web 的
<appSettings>
<add key="SimpleFactoryConfig" value="OpenSource.FlashElf.MVP.Model.TestDAL"/>
</appSettings>
Win 的
<appSettings>
<add key="SimpleFactoryConfig" value="OpenSource.FlashElf.MVP.Model.WebServiceDAL"/>
</appSettings>
现在解决方案里是这样的 DAL ,正好把两个 DAL 都配置上
其实怎么配都一样的都,配置成 WebServiceDAL 或 TestDAL 也没问题
我就是为了 demo
好了基本差不多了,剩下的写不动了我也累了、MVP 部分都讲了基本
剩下的都是 简单工程模式的东西了。
代码都没有说的多,剩下的代码就和 PetShop 差不多了
没有用任何数据库,都是临时生成的数据。
心得
如果没有代码生成器,写这种模式太累了,n个工程虽然代码不多,不过要在实际环境一定能累死人
而且会出现各种各样的问题,要建立在良好的设计之上是必须的(我就是因为没设计直接敲代码改来改去的)
个人认为 mvp 模式【不适合】容易更改的项目,或很小的项目
不过一心想把小的往大了做的没办法。
这么做 mvp 也不知道对不对,第一次写这种模式、反正已经发出去了、各位看官提点意见
我这就是一个demo 可不要太苛刻了呵呵。
错别字一定很多,慢慢改吧先发了在说;^_^.
最后:
代码下载 (61k)
本文仅发布于: http://www.cnblogs.com/ 、转载请注明,作者等相关信息