一步一步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来进行单元测试的目的就达到了。

 

 

 

 

 

posted @ 2013-03-28 22:02  小牧ah  阅读(1459)  评论(8)    收藏  举报