一步一步MVP
一、基本的MVP模式实现
为了更好的理解MVP,我们首先实现一个基本的MVP模式,再逐步演进到一个实用的MVP模式,我们还是先来看这么一段代码。
首先定义视图View:
View Code
/// <summary> /// MVP模式中的视图只负责输入、输出 /// </summary> public interface IHomeView { /// <summary> ///方法负责输出: View接收数据,需要提供接口方法接收Presenter传递的数据 /// </summary> /// <param name="categories"></param> void SetCategories(List<Category> categories); /// <summary> ///事件负责输入: View向Presenter发送请求,通过P向V订阅事件,参数可以通过事件参数或者视图上的公开属性传递 /// </summary> event EventHandler ReLoadData; }
然后定义展示器Presenter:
View Code
public class HomePresenter { private IHomeView _view; /// <summary> /// 业务数据提供类,可属性注入 /// </summary> public IModuleService ModuleService { set; get; } public HomePresenter(IHomeView view) { this._view = view; this._view.ReLoadData += ReloadDataHandler; this.ModuleService=new MockModuleService(); } void ReloadDataHandler(object sender, EventArgs e) { this._view.SetCategories(ModuleService.GetAll()); } }
再定义个业务逻辑和数据提供者Model:
View Code
public interface IModuleService { List<Category> GetAll(); } public class MockModuleService : IModuleService { public List<Category> GetAll() { return new List<Category>() { new Category(){Id = 1,Title = "设计模式"}, new Category(){Id = 2,Title = "架构模式"} }; } }
定义视图实现:
View Code
public partial class HomeForm : Form, IHomeView { private HomePresenter _presenter; public HomeForm() { InitializeComponent(); _presenter = new HomePresenter(this); this.listBoxCategory.Format += listBoxCategory_Format; } void listBoxCategory_Format(object sender, ListControlConvertEventArgs e) { var category = e.ListItem as Category; if (category == null) return; e.Value = category.Title; } public void SetCategories(List<Category> categories) { this.listBoxCategory.DataSource = categories; } public event EventHandler ReLoadData; private void btnReload_Click(object sender, EventArgs e) { if (ReLoadData != null) ReLoadData(this, e); } }
MVP基本模式的实现中反应出各个对象的职责,Model只负责提供业务数据,View只负责输入和输出,Presenter负责控制和协调,View对Presenter和Model都是无知的,Model只应对Presenter公开,P和V的通讯方式严格上来讲最好是单向通讯的,V触发一个向P请求数据的事件,P通过订阅V的事件来响应处理V的请求,并通过V提供的更新接口方法来向V推数据。
再来看我们使用MVP模式主要的目的是什么了,更好的职责分离?视图,模型,展示器相互隔离方便Mock,方便分开测试?多视图,或者说多种类型的客户端技术重用相同的展示器逻辑?上面的基本实现显然无法满足所有的需求。
基本实现真的实现了M,V,P之间的隔离么,我们一眼看到V的实现中直接创建了P的过程,V持有一个P的实例,V就能知道P的实现细节(P中的那些公开方法), 我们能抵制住不直接操作P的诱惑么,在实际编码的过程中,你很快就会发现,V既然已经知道有P的存在了,那为什么不直接向P请求数据,还用事件来请求以达到V和P隔离的目的了,这多麻烦,很快代码中就充满_presenter.GetX();_prensenter.GetY()....,一旦开了这个口,就违反了View只负责输入输出的原则,View本不应该知晓如何取数据,要使用什么数据由P说了算。
二、View基类通过模板方法创建Presenter的实现
那如何才能不让View知晓Presenter的实现细节了,很快我们就能想到至少2个方法: 1、最简单的方法就是让P中的所有的方法都是私有,没有公开方法,那就无从知晓P中的细节了,这个方法简单粗暴但是可行,另外如果对P做单元测试,这可就要多做点额外的工作了。2、让View不持有Presenter,那也就达到了不让View知晓Presenter的实现细节,达到职责隔离的目的了。排除第一种方法,第二种方法要如何让View不持有Presenter,又要保持这种View知道具体由哪个Presenter为View提供服务的弱依赖了。如果我们提供一个View的基类来保持一个私有的Presenter实例,那View的实现就不知道有Presenter这回事了。
1、定义一个View的基类
View Code
public class WinFromViewBase : Form, IView { private object _presenter; protected WinFromViewBase() { //Prensenter的创建过程移到了基类中,私有P了,View实现不知道了P的细节了 _presenter = CreatePresenter(); } /// <summary> /// 基类模板方法创建展示器,由View实现类提供模板方法的实现 /// </summary> /// <returns></returns> public virtual object CreatePresenter() { if (LicenseManager.CurrentContext.UsageMode == LicenseUsageMode.Designtime) { return null; } else { throw new NotImplementedException(string.Format("{0} must override the CreatePresenter method.", this.GetType().FullName)); } } }
2、看看View的实现类吧
public partial class HomeForm : WinFromViewBase, IHomeView { private HomePresenter _presenter; public HomeForm() { InitializeComponent(); _presenter = new HomePresenter(this); this.listBoxCategory.Format += listBoxCategory_Format; } /// <summary> /// 实现基类模板方法创建Presenter,让基类保持私有实例,实现类不保持P的实例了 /// </summary> /// <returns></returns> public override object CreatePresenter() { return new HomePresenter(this); } }
现在View实现中没有保持Presenter的实例,也调不到基类中的私有_presenter,解决了向V隔离P的的目的。但是这里V仍然需要知道如何来创建Presenter。能不能不让View实现类来创建这个P的实例了?答案当然是肯定的了。
三、View属性反射创建Presenter的实现
对于上面在哪里创建的展示器的问题,你可能已经想到了,我们直接在View的基类中创建P吧。但是我们需要让View的基类知道创建哪个具体的Presenter才行。我们试着这么来做。用特性来记录由哪个具体P为View提供服务。
1、定义一个Attribute描述View的实现需要使用哪个展示器
public class PresenterTypeAttribute : Attribute { private Type _presenterType; public PresenterTypeAttribute(Type presenterType) { _presenterType = presenterType; } public Type PresenterType { get { return _presenterType; } set { _presenterType = value; } } }
我们这样来描述,View实现类上记录了由HomePresenter来为View提供服务:
[PresenterTypeAttribute(typeof(HomePresenter))] public partial class HomeForm : WinFromViewBase, IHomeView
再来看看View基类如何根据特性来创建P:
View Code
public class WinFromViewBase : Form, IView { private object _presenter; protected WinFromViewBase() { //Prensenter的创建过程移到了基类中,私有P了,View实现不知道了P的细节了 _presenter = this.SelfRegister(this); } /// <summary> /// 通过在基类中根据自定义特性中记录的类型来反射创建Presenter /// </summary> /// <param name="view"></param> /// <returns></returns> private object SelfRegister(IView view) { var attributes = view.GetType().GetCustomAttributes(typeof(PresenterTypeAttribute), true); if (attributes.Length == 0) return null; foreach (var viewAttribute in attributes) { if (!(viewAttribute is PresenterTypeAttribute)) continue; Type presenterType = (viewAttribute as PresenterTypeAttribute).PresenterType; return Activator.CreateInstance(presenterType, view); } return null; } }
这里通过反射View上的特性,由View基类WinformViewBase直接负责了P的创建。这样View也就彻底不知道P的实现细节,也不知道P如何创建了。
四、彻底IOC的实现吧
上面的过程是一个逐步解除依赖的过程,但终极的解除依赖的手段还是得由IOC容器来完成,那个View上挂个特性瓜皮帽的办法也让某些渴望纯净控制的人无法忍受了。View本只应该负责输入输出,由谁来给他提供服务都不需要知道,这一切都可以交由IOC容器来完成。纯净的世界应该是这样的。
这里只有View:
View Code
/// <summary> /// MVP模式中的视图只负责输入、输出 /// </summary> public interface IHomeView : IView { /// <summary> ///方法负责输出: View接收数据,需要提供接口方法接收Presenter传递的数据 /// </summary> /// <param name="categories"></param> void SetCategories(List<Category> categories); /// <summary> ///事件负责输入: View向Presenter发送请求,通过P向V订阅事件,参数可以通过事件参数或者视图上的公开属性传递 /// </summary> event EventHandler ReLoadData; }
还有View的实现:
[PresenterType(typeof(HomePresenter))] public partial class HomeForm : IHomeView { public HomeForm() { InitializeComponent(); this.listBoxCategory.Format += listBoxCategory_Format; } void listBoxCategory_Format(object sender, ListControlConvertEventArgs e) { var category = e.ListItem as Category; if (category == null) return; e.Value = category.Title; } public void SetCategories(List<Category> categories) { this.listBoxCategory.DataSource = categories; } public event EventHandler ReLoadData; private void btnReload_Click(object sender, EventArgs e) { if (ReLoadData != null) ReLoadData(this, e); } }
还有Presenter:
View Code
public class PresenterBase<T> where T : IView { private T _view; public PresenterBase(T view) { this.View = view; } public T View { get { return _view; } set { _view = value; } } }
View Code
public class HomePresenter : PresenterBase<IHomeView> { /// <summary> /// 业务数据提供类,可属性注入 /// </summary> [Inject] public IModuleService ModuleService { set; get; } /// <summary> /// 构造函数注入 /// </summary> public HomePresenter(IHomeView view) : base(view) { this.View.ReLoadData += ReloadDataHandler; } private void ReloadDataHandler(object sender, EventArgs e) { this.View.SetCategories(ModuleService.GetAll()); } }
再来看看我们的IOC容器做了什么:
View Code
public class WinFormInjectModule : Ninject.Modules.NinjectModule { public override void Load() { Bind<IModuleService>().To<MockModuleService>(); Bind<IHomeView>().To<HomeForm>(); Bind<HomePresenter>().ToSelf(); } }
View Code
public class IocContainer { private static IKernel _kernel; public static IKernel Container { get { if (_kernel == null) _kernel = new StandardKernel(new WinFormInjectModule()); return _kernel; } } }
最后来看看我们的客户端类吧:
View Code
/// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var p = IocManager.IocContainer.Container.Get<IService.HomePresenter>(); var frm = p.View as Form; Application.Run(frm); }
至此为止,由IOC容器来管理我们的View,Presenter,Model的创建过程,这里由Ninject.Module.NinjectModule的实现类来集中管理依赖,MVP的终极模式各自分离,各自可独立MOCK来进行单元测试的目的就达到了。


浙公网安备 33010602011771号