Spiga

.NET平台上的Model-View-Presenter模式实践

2010-01-28 21:48 by T2噬菌体, 5074 visits, 收藏, 编辑

为什么要写这篇文章

      笔者当前正在负责研究所中一个项目,这个项目基于.NET平台,初步拟采用C/S部署体系,所以选择了Windows Forms作为其UI。经过几此迭代,我们发现了一个问题:虽然业务逻辑已经封装到Services层中,但诸多的UI逻辑仍然弥漫在各个事件Listener中,使得UI显得臃肿不堪,并且存在诸多重复性代码。另外,需求提供方说,根据实际需要,不排除将部署结构改为B/S的可能性,甚至可能会要求此系统同时支持C/S和B/S两种部署方式。那么,如果保持目前将UI逻辑编码到Windows Forms中的方式,到时这些UI逻辑将无法复用,修改部署方式的代价很大。

      为了解决以上两个问题,笔者和相关人员商量后,决定引入既有成熟模式,重新设计表示层的架构方式,并重构既有代码。

      提到表示层(Presentation Layer)的模式,我想大家脑海中第一个闪过的很可能是经典的MVC(Model-View-Controller)。我最初也准备使用MVC,但经过分析和实验后,我发现MVC并不适合目前的情况,因为MVC的结构相对复杂,Model和View之间要实现一个Observer模式,并实现双向通信。这样重构起来Services层也必须修改。我并不想修改Services层,而且我想将View和Model彻底隔离,因为我个人并不喜欢View和Model直接通信的架构方式。最终,我选择了MVP(Model-View-Presenter)模式。

      经过两天的重构和验证,目前已经将MVP正式引入项目的表示层,并且解决了上文提到的两个问题。在这期间,积累了少许关于在.NET平台上实践MVP的经验,在这里汇集成此文,和朋友们共享。

UI与P Logic

      首先,我想先明确一下UI和P Logic的概念。

      表示层可以拆分为两个部分:User Interface(简称UI)和Presentation Logic(简称P Logic)。

      UI是系统与用户交互的界面性概念,它的职责有两个——接受用户的输入和向用户展示输出。UI应该是一个纯静态的概念,本身不应包含任何逻辑,而单纯是一个接受输入和展示输出的“外壳”。例如,一个不包含逻辑的Windows Form,一张不包含逻辑的页面,一个不包含逻辑的Flex界面,都属于UI。

      P Logic是表示层应有的逻辑性内容。例如,某个文本内容不能为空,当某个事件发生时获取界面上哪些内容,这都属于P Logic。应该指出,P Logic应该是抽象于具体UI的,它的本质是逻辑,可以复用到任何与此逻辑相符的UI。

      UI与P Logic之间的联系是事件,UI可以根据用户的动作触发各种事件,P Logic响应事件并执行相应的逻辑。P Logic对UI存在约束作用,P Logic规定一套UI契约,UI要根据契约实现,才能被相应的P Logic调用。

      下图展示了UI与P Logic的结构及交互原理。

图1、UI与P Logic

Model-View-Presenter模式

      MVP模式最早由Taligent的Mike Potel在《MVP: Model-View-Presenter The Taligent Programming Model for C++ and Java》(点击这里下载)一文中提出。MVP的提出主要是为了解决MVC模式中结构过于复杂和模型-视图耦合性过高的问题。MVP的核心思想是将UI分离成View,将P Logic分离成Presenter,而业务逻辑和领域相关逻辑都分离到Model中。View和Model完全解除耦合,不再像MVC中实现一个Observer模式,两者的通信则依靠Presenter进行。Presenter响应View接获的用户动作,并调用Model中的业务逻辑,最后将用户需要的信息返回给View。

      下图直观表示了MVP模式:

图2、MVP模式

      图2清楚地展示了MVP模式的几个特点:

      1、View和Model完全解耦,两者不发生直接关联,通过Presenter进行通信。

      2、Presenter并不是与具体的View耦合,而是和一个抽象的View Interface耦合,View Interface相当于一个契约,抽象出了对应View应实现的方法。只要实现了这个接口,任何View都可以与指定Presenter兼容,从而实现了P Logic的复用性和视图的无缝替换。

      3、View在MVP里应该是一个“极瘦”的概念,最多也只能包含维护自身状态的逻辑,而其它逻辑都应实现在Presenter中。

      总的来说,使用MVP模式可以得到以下两个收益:

      1、将UI和P Logic两个关注点分离,得到更干净和单一的代码结构。

      2、实现了P Logic的复用以及View的无缝替换。

在.NET平台上实现MVP模式

      这一节通过一个示例程序展示在.NET平台上实现MVP的一种实践方法。本来想通过我目前负责的实际项目中的代码片段作为Demo,但这样做存在两个问题:一是这样做可能会违反学校的保密守则,二是这个项目应用了许多其他框架和模式,如通过Unity实现依赖注入,通过PostSharp实现AOP来负责异常处理和事务管理等,通过NHibernate实现的ORM等等,这样如果读者不了解系统整体架构就很难完全读懂代码片段,MVP模式不够突出。因此,我专门为这篇文章实现了一个Demo,其中的MVP实践方式与实际项目中是一致的,而且Demo规模小,排除了其他干扰,使得读者更容易理解其中的MVP实现方式。

      这个简单的Demo运行效果如下:

图3、Demo界面

      这个Demo的功能如下:这是一个简单的点餐软件。系统中存有餐厅所有菜品的信息,客户只需在界面右侧输入菜品名称和数量,单击“添加”按钮,菜品就会被添加到左侧点餐列表,并显示此菜品详细信息。如果所点菜品不存在则软件会给出提示。另外,在左侧已点餐品列表中右键单击某个条目,在弹出菜单中点击“删除”,则可将此菜品从列表删除。

      下面分步骤介绍应用了MVP模式的实现方式。

第一步,解决方法及工程结构

      这个Demo共有三个工程,MVPSimple.Model为Mock方式实现的Services,作为Model;MVPSimple.Presenters为Presenter工程,其中包括Presenter和View Interface;MVPSimple.WinUI为View的Windows Forms实现。

第二步,构建Mock方式的Services

      因为重点在于表示层,所以这里的Services使用了Mock方式,并没有包含真正的业务领域逻辑。其中MVPSimple.Model工程里两个文件的代码如下:

      FoodDto.cs:

using System;

namespace MVPSimple.Model
{
    /// <summary>
    /// 表示菜品类别的枚举类型
    /// </summary>
    public enum FoodType
    {
        主菜 = 1,
        汤 = 2,
        甜品 = 3,
    }

    /// <summary>
    /// 菜品的Data Transfer Object
    /// </summary>
    public class FoodDto
    {
        /// <summary>
        /// ID,标识字段
        /// </summary>
        public Int32 ID { get; set; }

        /// <summary>
        /// 菜品名称
        /// </summary>
        public String Name { get; set; }
        
        /// <summary>
        /// 菜品类型
        /// </summary>
        public FoodType Type { get; set; }

        /// <summary>
        /// 菜品价格
        /// </summary>
        public Double Price { get; set; }

        /// <summary>
        /// 点菜数量
        /// </summary>
        public Int32 Amount { get; set; }
    }
}

      FoodServices.cs:

using System;
using System.Collections.Generic;

namespace MVPSimple.Model
{
    /// <summary>
    /// 菜品Services的Mock实现
    /// </summary>
    public class FoodServices
    {
        private IList<FoodDto> foodList = new List<FoodDto>();

        /// <summary>
        /// 默认构造函数,初始化各个菜品
        /// </summary>
        public FoodServices()
        {
            this.foodList.Add(
                new FoodDto()
                {
                    ID = 1,
                    Name = "牛排",
                    Price = 60.00,
                    Type = FoodType.主菜,
                }
            );

            this.foodList.Add(
                new FoodDto()
                {
                    ID = 2,
                    Name = "法式蜗牛",
                    Price = 120.00,
                    Type = FoodType.主菜,
                }
            );

            this.foodList.Add(
                new FoodDto()
                {
                    ID = 3,
                    Name = "水果沙拉",
                    Price = 58.00,
                    Type = FoodType.甜品,
                }
            );

            this.foodList.Add(
                new FoodDto()
                {
                    ID = 4,
                    Name = "奶油红菜汤",
                    Price = 15.00,
                    Type = FoodType.汤,
                }
            );

            this.foodList.Add(
                new FoodDto()
                {
                    ID = 5,
                    Name = "杂拌汤",
                    Price = 20.00,
                    Type = FoodType.汤,
                }
            );
        }

        /// <summary>
        /// 按照菜品名称获取菜品详细信息
        /// </summary>
        /// <param name="foodName">菜品名称</param>
        /// <returns>含有指定菜品信息的DTO</returns>
        public FoodDto GetFoodDetailByName(String foodName)
        {
            foreach (FoodDto f in this.foodList)
            {
                if (f.Name.Equals(foodName))
                {
                    return f;
                }
            }

            return new FoodDto() { ID = 0 };
        }
    }
}

第三步,通过View Interface规定View契约

      如果想实现Presenter和View的交互和无缝替换,必须在它们之间规定一个契约。一般来说,每一张界面(注意是界面不是视图)都应该对应一个View接口,不过由于Demo只有一个页面,所以也只有一个View接口。

      这里需要特别强调,View接口必须抽象于任何具体视图而服务于Presenter,所以,View接口中绝不能出现任何与具体视图相关的元素。例如,我们的Demo中是使用Windows Forms作为视图实现,但View接口中绝不可出现与Windows Forms相耦合的元素,如返回一个Winform的TextBox。因为如果这样做的话,使用其他技术实现的View就无法实现这个接口了,如使用Web Forms实现,而Web Forms是不可能返回一个Winform的TextBox的。

      下面给出视图接口的代码。

      IMainView.cs:

using System;
using System.Collections.Generic;
using MVPSimple.Model;

namespace MVPSimple.Presenters
{
    /// <summary>
    /// MainView的接口,所有MainView必须实现此接口,此接口暴露给Presenter
    /// </summary>
    public interface IMainView
    {
        /// <summary>
        /// View上的菜品名称
        /// </summary>
        String foodName { get; set; }

        /// <summary>
        /// View上点菜数量
        /// </summary>
        Int32 Amount { get; set; }

        /// <summary>
        /// 判断某一菜品是否已经存在于点菜列表中
        /// </summary>
        /// <param name="foodName">菜品名称</param>
        /// <returns>结果</returns>
        bool IsExistInList(String foodName);

        /// <summary>
        /// 将某一菜品加入点菜列表
        /// </summary>
        /// <param name="food">菜品DTO</param>
        void AddFoodToList(FoodDto food);
        
        /// <summary>
        /// 将某一已点菜品从列表中移除
        /// </summary>
        /// <param name="foodName">欲移除的菜品名称</param>
        void RemoveFoodFromList(String foodName);

        /// <summary>
        /// View显示提示信息给用户
        /// </summary>
        /// <param name="message">信息内容</param>
        void ShowMessage(String message);

        /// <summary>
        /// View显示确认信息并返回结果
        /// </summary>
        /// <param name="message">信息内容</param>
        /// <returns>用户回答是确定还是取消。True - 确定,False - 取消</returns>
        bool ShowConfirm(String message);
    }
}

      可以看到,IMainView抽象了如图3所示的界面,但又不包含任何与Windows Forms相耦合的元素,因此如果需要,以后完全可以使用Web Forms、WPF或SL等技术实现这个接口。

第四步,实现Presenter

      上文说过,一个界面应该对应一个Presenter,这个Demo里只有一个界面,所以只有一个Presenter。Presenter仅于视图接口耦合,而并不和具体视图耦合,最好证据就是Presenter工程根本没有引用WinUI工程!代码如下:

      MainPresenter.cs:

using System;
using System.Collections.Generic;
using MVPSimple.Model;

namespace MVPSimple.Presenters
{
    /// <summary>
    /// MainView的Presenter
    /// </summary>
    public class MainPresenter
    {
        /// <summary>
        /// 当前关联View
        /// </summary>
        public IMainView View { get; set; }

        /// <summary>
        /// 默认构造函数,初始化View
        /// </summary>
        /// <param name="view">MainView对象</param>
        public MainPresenter(IMainView view)
        {
            View = view;
        }

        #region Acitons

        /// <summary>
        /// Action:将所点菜品增加到点菜列表
        /// </summary>
        public void AddFoodAction()
        {
            if (String.IsNullOrEmpty(View.foodName))
            {
                View.ShowMessage("请选输入菜品名称");
                return;
            }
            if (View.Amount <= 0)
            {
                View.ShowMessage("点菜的份数至少要是一份");
                return;
            }
            if (View.IsExistInList(View.foodName))
            {
                View.ShowMessage(String.Format("菜品【{0}】已经在您的菜单中", View.foodName));
                return;
            }

            FoodServices foodServ = new FoodServices();
            FoodDto food = foodServ.GetFoodDetailByName(View.foodName);
            if (food.ID == 0)
            {
                View.ShowMessage(String.Format("抱歉,本餐厅没有菜品【{0}】",View.foodName));
                return;
            }

            View.AddFoodToList(food);
        }

        /// <summary>
        /// Action:从点菜列表移除某一菜品
        /// </summary>
        /// <param name="foodName">被移除菜品的名称</param>
        public void RemoveFoodAction(String foodName)
        {
            if (View.ShowConfirm("确定要删除吗?"))
            {
                View.RemoveFoodFromList(foodName);
            }
        }

        #endregion
    }
}

第五步,实现View

      这里我们使用Windows Forms实现View。如果朋友们有兴趣,完全可以自己试着用Web或WPF实现以下视图,同时可以验证P Logic的可复用性和视图无缝替换,亲身体验一下MVP模式的威力。Winform的View代码如下。

      frmMain.cs:

using System;
using System.Windows.Forms;
using MVPSimple.Model;
using MVPSimple.Presenters;

namespace MVPSimple.WinUI
{
    /// <summary>
    /// MainView的Windows Forms实现
    /// </summary>
    public partial class frmMain : Form, IMainView
    {
        /// <summary>
        /// 相关联的Presenter
        /// </summary>
        private MainPresenter presenter;

        /// <summary>
        /// 默认构造函数,初始化Presenter
        /// </summary>
        public frmMain()
        {
            InitializeComponent();
            this.presenter = new MainPresenter(this);
        }

        #region IMainView Members

        /// <summary>
        /// View上的菜品名称
        /// </summary>
        public String foodName
        {
            get { return this.tbFoodName.Text; }
            set { this.tbFoodName.Text = value; }
        }

        /// <summary>
        /// View上点菜数量
        /// </summary>
        public Int32 Amount
        {
            get { return (Int32)this.tbAmount.Value; }
            set { this.tbAmount.Value = (Decimal)value; }
        }

        /// <summary>
        /// 判断某一菜品是否已经存在于点菜列表中
        /// </summary>
        /// <param name="foodName">菜品名称</param>
        /// <returns>结果</returns>
        public bool IsExistInList(String foodName)
        {
            foreach (ListViewItem i in this.lvFoods.Items)
            {
                if (i.Text == foodName)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 将某一菜品加入点菜列表
        /// </summary>
        /// <param name="food">菜品DTO</param>
        public void AddFoodToList(FoodDto food)
        {
            ListViewItem item = new ListViewItem();
            Double price = food.Price * (Double)this.tbAmount.Value;

            item.Text = food.Name;
            item.SubItems.Add(food.Type.ToString());
            item.SubItems.Add(this.tbAmount.Value.ToString());
            item.SubItems.Add(price.ToString());
            this.lvFoods.Items.Add(item);
        }

        /// <summary>
        /// 将某一已点菜品从列表中移除
        /// </summary>
        /// <param name="foodName">欲移除的菜品名称</param>
        public void RemoveFoodFromList(String foodName)
        {
            foreach (ListViewItem i in this.lvFoods.Items)
            {
                if (i.Text == foodName)
                {
                    this.lvFoods.Items.Remove(i);
                }
            }
        }

        /// <summary>
        /// View显示提示信息给用户
        /// </summary>
        /// <param name="message">信息内容</param>
        public void ShowMessage(String message)
        {
            MessageBox.Show(message, "信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }

        /// <summary>
        /// View显示确认信息并返回结果
        /// </summary>
        /// <param name="message">信息内容</param>
        /// <returns>用户回答是确定还是取消。True - 确定,False - 取消</returns>
        public bool ShowConfirm(String message)
        {
            DialogResult result = MessageBox.Show(message, "确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
            return DialogResult.OK == result;
        }

        #endregion

        #region Event Listeners

        private void btnAdd_Click(object sender, EventArgs e)
        {
            this.presenter.AddFoodAction();
        }

        private void miDeleteFood_Click(object sender, EventArgs e)
        {
            if (this.lvFoods.SelectedItems.Count != 0)
            {
                String foodName = this.lvFoods.SelectedItems[0].Text;
                this.presenter.RemoveFoodAction(foodName);
            }
        }

        #endregion
    }
}

      可以看到,使用了MVP后,View的代码变的非常干净整洁,以前充斥着厚重表示逻辑的事件Listener方法变得“瘦”了许多。

      完成以上几步后,就可以运行这个Demo看效果了。

总结

      这篇文章首先讨论表示层的组成,说明User Interface和Presentation Logic是表示层的两个重要组成部分,并分别说明了两者的作用及交互方式。接着讨论了MVP模式。最后,通过一个Demo展示了在.NET平台上实现MVP的一种实践方式。应该说,MVP很类似简化了MVC,MVP不但可以分离关注、使得代码变得干净整洁、并实现P Logic的复用,而且实现起来比MVC在结构上要简单很多。MVP是一种模式,本身有诸多实现方式,本文只是介绍了笔者使用的一种实践,朋友们也可以在此基础上摸索自己的实践。

Creative Commons License

本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名张洋(包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

Add your comment

41 条回复

  1. #1楼 xuefly      2010-01-28 22:07
    先坐沙发
     回复 引用 查看   
  2. #2楼 Gsanidt      2010-01-28 22:12
    xuefly速度真快!
     回复 引用 查看   
  3. #3楼 xuefly      2010-01-28 22:19
    哈哈 刚好可以看懂 幸亏我刚读完一本书

    图上的选用WPF做表现层的那个架构就是用的MVP 最后第12章有完整代码
    Professinal Enterprise .NET.pdf推荐大家
    这里下载
     回复 引用 查看   
  4. #4楼 LanceZhang      2010-01-28 22:19
    精彩!如果能提供一个Web Form的例子就更好了
     回复 引用 查看   
  5. #5楼 dewin      2010-01-28 22:27
    好像没说清楚mvc跟mvp的区别啊,是不是mvc中的model与view之间耦合关系紧密,并通过control来把两者之间的联系在一起,且model是专门写逻辑和实体的,而mvp中model是专门写实体,present是专门写逻辑的。各位说说mvp与mvc的本质区别?
     回复 引用 查看   
  6. #6楼 Gsanidt      2010-01-28 22:27
    引用xuefly:
    哈哈 刚好可以看懂 幸亏我刚读完一本书

    图上的选用WPF做表现层的那个架构就是用的MVP 最后第12章有完整代码
    Professinal Enterprise .NET.pdf推荐大家
    这里下载

    点击下载
     回复 引用 查看   
  7. #7楼 黄超      2010-01-28 22:29
    感觉跟FLEX开发有点相似,以事件为主来开发。关注!
     回复 引用 查看   
  8. #8楼 Gsanidt      2010-01-28 22:29
    最近我也在做一个打包工具的版本更新,仔细一看原来的代码也是很混乱,我也可以参考一下MVP试试!
     回复 引用 查看   
  9. #9楼 Grant Liu      2010-01-28 22:30
    其实P和V都是依赖接口,然后V只负责呈现数据,具体显示什么数据要P来指示。所以逻辑全部压缩到P中,V就解脱了。个人小观点。
     回复 引用 查看   
  10. #10楼[楼主] EricZhang(T2噬菌体)      2010-01-28 22:37
    引用LanceZhang:精彩!如果能提供一个Web Form的例子就更好了

    呵呵,其实本来想同时使用Winform和Webform实现两套视图,不过懒得搞了
     回复 引用 查看   
  11. #11楼[楼主] EricZhang(T2噬菌体)      2010-01-28 22:38
    引用dewin:好像没说清楚mvc跟mvp的区别啊,是不是mvc中的model与view之间耦合关系紧密,并通过control来把两者之间的联系在一起,且model是专门写逻辑和实体的,而mvp中model是专门写实体,present是专门写逻辑的。各位说说mvp与mvc的本质区别?

    我写这篇文章主要奔MVP去的,没打算说明MVP和MVC的区别。囧。。。
     回复 引用 查看   
  12. #12楼 xuefly      2010-01-28 22:54
    我觉得因为Web是无状态的,来自浏览器的数据太凌乱和离散,最好组织一下,成为一个有联系的数据有机体(View Model),然后我们在服务器编程和处理着就方便了。所以才需要V和M的联系比P和M紧密一些,因为MVP处理的情况往往不是无状态的,操作起数据来比较容易,但是MVP中的P定义接口应该也是为了把凌乱的离散的P数据整成一个有关系的整体。这样说的话,MVC和MVP的本质区别在于一个使用Model来整理数据另一个使用接口来整理数据。个人理解。
     回复 引用 查看   
  13. #13楼 温景良(Jason)      2010-01-28 23:09
    群里写mvp的太少了,希望楼主能多写一些这方面的文章.之前找了很久都没有找打到几篇,MVP模式有的时候比mvc适合.
     回复 引用 查看   
  14. #14楼 温景良(Jason)      2010-01-28 23:21
    oyeah,我的好好研究下,顺便重构一下以前项目的代码
     回复 引用 查看   
  15. #15楼 xuefly      2010-01-28 23:27
    WebForm是MVP是不是?
    虽然那个P(Page)我们没有定义自定义接口 但是微软帮我们定义了IHttpHandel 这个接口组织管理了数据
    微软要实现Web的MVP 于是进一步把Asp.Net WebForm弄成了状态化 ViewState就出现了
    微软用继承自IHttpHandel的Page基类组织管理了来自浏览器的数据 通过Page基类的HttpContext HttpResponse HttpRequest Server等这些个封装好的数据对象功能对象等对象非常粗的组织管理了来自浏览器的凌乱的和离散的数据
    所以WebForm就是MVP 只是太粗了
     回复 引用 查看   
  16. #16楼[楼主] EricZhang(T2噬菌体)      2010-01-28 23:42
    @xuefly
    Web Form模型可能更多是Page Controller模式吧,和MVP还是不太一样
     回复 引用 查看   
  17. #17楼 萧寒      2010-01-28 23:57
    @BZ

    MVP 模式我用起来就一个字评价它‘累’;整个系统充斥着大量的接口;
     回复 引用 查看   
  18. #18楼 xuefly      2010-01-29 00:08
    @萧寒
    接口就是约束,有了约束可以简便的管理数据,如果法律规定的面面俱到的话执法人的工作就很轻松,只需要照着法律办事。但是立法人很累,立法机关已经很努力的工作了,可是还是立不好法。有时候还需要废除不适应新时代的旧法律颁布新法律,总是不能完备。着实很累。
     回复 引用 查看   
  19. #19楼[楼主] EricZhang(T2噬菌体)      2010-01-29 00:14
    @xuefly
    这个比喻好
     回复 引用 查看   
  20. #20楼 xuefly      2010-01-29 00:27
    @EricZhang(T2噬菌体)
    谢谢 睡觉了 明天还得上班
     回复 引用 查看   
  21. #21楼 Kevin Zou      2010-01-29 08:35
    終于有人開始關注UI了,我覺得UI的模式到現在是最弱的,理想中的UI是快速開發,可重用,可無限靈活的客制化
     回复 引用 查看   
  22. #22楼 一线风      2010-01-29 09:06
    不错,楼主的贴子总是能给出新思想。
     回复 引用 查看   
  23. #23楼 Hcs66      2010-01-29 09:07
    在WPF中也流行使用类似的MVVM模式来开发。
     回复 引用 查看   
  24. #24楼 思臣      2010-01-29 10:46
    写得好!只知用MVC,还不知MVP,看来偶孤落寡文呀~ 谢LZ分享。
     回复 引用 查看   
  25. #25楼 Will Meng      2010-01-29 12:50
    可以搭配一个controller,来控制presenter和数据库的交互。
    而且了解一下微软的UI框架CAB的话,你会发现,好多winform的项目都可以用CAB来实现了。
    而且对于mvp的分层来说,当你的view开始使用的是datagrid显示数据,而后来客户要求你使用chart显示数据,那么你只需要改动极少的代码就可以做到了。
     回复 引用 查看   
  26. #26楼 JackieHan      2010-01-29 13:01
    很早的时候就看MVP,和上面的人感觉一样,用着类!好像还有个代码生成的工具,记得不太清楚了
     回复 引用 查看   
  27. #27楼 longli      2010-01-29 16:19
    确实有很多程序是需要c/s和b/s共用除了UI的其余部分的,你实现的这种方式给了我很好的启发,呵呵,谢谢了。但是有一个小疑问,这样的实现方式,一旦有新的功能添加或修改,需要改动的部分会不会就相对多了一些,尤其是View接口这种实现方式,貌似看起来P和V还是很紧密,呵呵,浅见
     回复 引用 查看   
  28. #28楼[楼主] EricZhang(T2噬菌体)      2010-01-29 16:23
    @longli
    面向接口编程很大的问题就是级联修改问题,即一旦需求变动,接口和实现都要跟着变,这个问题是本质性的,没办法克服。呵呵,这确实面向接口编程的一大缺陷。
     回复 引用 查看   
  29. #29楼 Jian, Li      2010-01-30 07:57
    的确写得很精彩,上一个项目才开始接触MVP,越用越觉得结构清晰、层次分明,尤其是在做单元测试时,做一个View的Mock直接对Presenter进行测试,如果用MVC,则好像没有无法对P层作单元测试。
     回复 引用 查看   
  30. #30楼 Jian, Li      2010-01-30 08:26
    的确,MVP最大的问题就是如果需要改动View,就要相应地改动IView,“级联修改"确实是其难以避免的代价。
     回复 引用 查看   
  31. #31楼 大石头      2010-01-30 09:08
    概念性太强了

    代码太多了,同样一个属性,得写多少回呀
     回复 引用 查看   
  32. #32楼[楼主] EricZhang(T2噬菌体)      2010-01-30 12:54
    @大石头
    如果有机会,建议你先实际使用一下,你就会发现,MVP其实是令代码减少了,而且得到了清晰、灵活和易测试的代码。
     回复 引用 查看   
  33. #33楼 东国先生      2010-02-10 16:03
    不错的架构,如果是webform中,想实现客户端验证,该如何做呢?
     回复 引用 查看   
  34. #34楼 harrygoo      2010-02-22 10:41
    感觉和struts的mvc差不多
     回复 引用 查看   
  35. #35楼 Henry Xi      2010-03-20 19:54
    很不错的架构,结构更清晰了,复用性也更强了!感谢LZ分享!
     回复 引用 查看   
  36. #36楼 redsnow      2010-05-25 11:08
    感觉代码有点问题,WinUI中,不应该出现引入FoodModel,因为WebUI只知道viewInterFace,更不应该直接使用FoodDto,这样做好象达不到上面介绍的目的。
     回复 引用 查看   
  37. #37楼 redsnow      2010-05-25 11:22
    如果我理解错了,LZ赶紧指正 嘿
     回复 引用 查看   
  38. #38楼 Kevin Zou      2010-11-03 09:37
    1.View需要驻留数据吗?
    像这个询问是否存在的方法public bool IsExistInList(String foodName)我觉得是不是要放在P中好一些

    2.如果上面成立,那那些Add,Remove方法应该也要维护这份数据

    3.个人觉得ShowConfirm方法作为IView的接口要谨慎一些,可能有时候某种UI为了方便是不需要ShowConfirm方法的
     回复 引用 查看   
  39. #39楼 Kevin Zou      2010-11-03 18:06
    4.像P中的AddFoodAction方法
    你的例子中是没有带参数的,而是通过View接口进行
    对于这一点,我在想是不是可以让AddFoodAction方法直接带入参数Name,Amount等
    这样似乎就和RemoveFoodAction(Name)方法统一了

    5.事件的绑定在View中完成?我觉得View只是发布事件,事件绑定应该在P中完成

     回复 引用 查看   
  40. #40楼 billzhao      2011-05-28 11:09
    好东东啊,学习收藏了
     回复 引用 查看   
  41. #41楼 乖乖我是[未注册用户]2011-07-26 23:12
    文章写的很干净 很漂亮!
     回复 引用   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1657115 BJ0m5qifZg8=