Thinking In Design Pattern——MVP模式演绎

目录

  • What Is MVP
  • Domain Model
  • StubRepositoty
  • IView & Presenter
  • View
  • Ioc容器StructureMap

开篇

忙碌的9月,工作终于落定,新公司里的框架是MVP+Linq,对于MVP虽然不熟,但有MVC的基础,花了两天时间研究了MVP,故作此博文,留作参考。 Model-View-Presenter(模型-视图-呈现器,MVP)模式的重点是让Presenter控制整个表示层的逻辑流。MVP模式由如下三个不同部分组成:

  • 模型表示视图显示或者修改的业务数据,包括业务逻辑和领域相关的逻辑。
  • 视图通过呈现器显示模型数据,并将用户输入委托给呈现器。
  • 呈现器被视图调用来显示从模型中“拉”出来的数据并处理用户输入。

What Is MVP

了解了MVP设计模式后,我以一个简单的例子阐述MVP模式在企业级架构中的应用,如下图给出了企业级分层设计的ASP.NET应用程序的典型体系结构(实际还要更复杂些):

下面的我将以一个简单的案例(出自《ASP.NET》设计模式)详解MVP思想的应用,当然MVP和MVC一样都是属于表现层的设计模式,我将参考上述两幅图中的分层思想来创建应用程序,下图为分层体系结构创建完毕时解决方案目录:

OK,接下来我们从头开始来创建我们的应用程序,首先我们要分清楚需求(建立一个简单的购物流程Demo),了解需求后我们再抽象出模型(Category,Product)。

建立简单的领域模型:

namespace Eyes.MVP.Model
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
public class Product
{
    public int Id { get; set; }
    public Category Category { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Description { get; set; }
}

接着,为Product和Category添加资源库的契约接口,该接口为业务实体持久化提供了标准方法,我建议把这部分代码放到infrastructure层中:

public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    Category FindBy(int id);
}

public interface IProductRepository
{
    IEnumerable<Product> FindAll();
    Product FindBy(int id);
}

最后添加领域服务类ProductService,基于接口编程的思想使用资源库契约接口(IxxxRepository)来协调Product和Category的操作:

public class ProductService
    {
        private ICategoryRepository _categoryRepository;
        private IProductRepository _productRepository;

        public ProductService(ICategoryRepository categoryRepository, IProductRepository productRepository)
        {
            _categoryRepository = categoryRepository;
            _productRepository = productRepository;
        }

        public Product GetProductBy(int id)
        {
            return _productRepository.FindBy(id);
        }

        public IEnumerable<Product> GetAllProductsIn(int categoryId)
        {
            return _productRepository.FindAll().Where(c => c.Category.Id == categoryId);
        }

        public Category GetCategoryBy(int id)
        {
            return _categoryRepository.FindBy(id);
        }

        public IEnumerable<Category> GetAllCategories()
        {
            return _categoryRepository.FindAll();
        }

        public IEnumerable<Product> GetBestSellingProducts()
        {
            return _productRepository.FindAll().Take(4);
        }
    }

建立Domain Model之后,需要为资源库(仓储)提供数据,所以创建StubRepository:

StubRepositoty

创建名为StubRepositoty的类库,DataContext为我们的资源库提供数据:

    /// <summary>
    /// Provider data to repositories
    /// </summary>
    public class DataContext
    {
        private readonly List<Product> _products;
        private readonly List<Category> _categories;

        public DataContext()
        {
            _categories = new List<Category>();
            var hatCategory = new Category {Id = 1, Name = "Hats"};
            var gloveCategory = new Category {Id = 2, Name = "Gloves"};
            var scarfCategory = new Category {Id = 3, Name = "Scarfs"};
            _categories.Add(hatCategory);
            _categories.Add(gloveCategory);
            _categories.Add(scarfCategory);
            _products = new List<Product>
                {
                    new Product {Id = 1, Name = "BaseBall Cap", Price = 9.99m, Category = hatCategory},
                    new Product {Id = 2, Name = "Flat Cap", Price = 5.99m, Category = hatCategory},
                    new Product {Id = 3, Name = "Top Hat", Price = 9.99m, Category = hatCategory}
                };
            _products.Add(new Product {Id = 4, Name = "Mitten", Price = 10.99m, Category = gloveCategory});
            _products.Add(new Product {Id = 5, Name = "Fingerless Glove", Price = 13.99m, Category = gloveCategory});
            _products.Add(new Product {Id = 6, Name = "Leather Glove", Price = 7.99m, Category = gloveCategory});
            _products.Add(new Product {Id = 7, Name = "Silk Scarf", Price = 23.99m, Category = scarfCategory});
            _products.Add(new Product {Id = 8, Name = "Woolen", Price = 14.99m, Category = scarfCategory});
            _products.Add(new Product {Id = 9, Name = "Warm Heart", Price = 87.99m, Category = scarfCategory});
        }

        public List<Product> Products
        {
            get { return _products; }
        }

        public List<Category> Categories
        {
            get { return _categories; }
        }
    }

当数据就为之后,我们就可以实现Model项目中定义的资源库契约接口:

public class ProductRepository : IProductRepository
    {
        public IEnumerable<Product> FindAll()
        {
            return new DataContext().Products;
        }

        public Product FindBy(int id)
        {
            Product productFound = new DataContext().Products.FirstOrDefault(prod => prod.Id == id);
            //set discription
            if (productFound != null)
            {
                productFound.Description = "orem ipsum dolor sit amet, consectetur adipiscing elit." +
                                           "Praesent est libero, imperdiet eget dapibus vel, tempus at ligula. Nullam eu metus justo." +
                                           "Curabitur sit amet lectus lorem, a tempus felis. " +
                                           "Phasellus consectetur eleifend est, euismod cursus tellus porttitor id.";
            }
            return productFound;
        }
    }

    public class CategoryRepository : ICategoryRepository
    {
        public IEnumerable<Category> FindAll()
        {
            return new DataContext().Categories;
        }

        public Category FindBy(int id)
        {
            return new DataContext().Categories.FirstOrDefault(cat => cat.Id == id);
        }
    }

这样我们就完成了StubRepository项目,有关Infrastructure和Repository我不做详细介绍,所以我简单处理。当然本片博客的核心是MVP,接下来详解View和Presenter关系。

View & Presenter

切换Presenter项目中,添加IHomeView接口,这个接口定义了电子商务网页的视图,在首页上显示商品目录以及最畅销的商品:

public interface IHomeView
   {
       IEnumerable<Category> CategoryList { set; }
   }

接着,定义一个IHomePagePresenter接口,这个接口的目的是实现代码松散耦合并有助于测试:

public interface IHomePagePresenter
    {
        void Display();
    }

最后,添加一个HomePagePresenter,这个呈现器从ProductService中检索到的Product和Category数据来填充视图属性,这儿完美体现了Presenter的作用:

   public class HomePagePresenter : IHomePagePresenter
    {
        private readonly IHomeView _view;
        private readonly ProductService _productService;

        public HomePagePresenter(IHomeView view, ProductService productService)
        {
            _view = view;
            _productService = productService;
        }

        public void Display()
        {
            _view.TopSellingProduct = _productService.GetBestSellingProducts();
            _view.CategoryList = _productService.GetAllCategories();
        }
    }

接下来是包含一个分类中所有商品的视图ICategoryProductsView:

public interface ICategoryProductsView
    {
        int CategoryId { get; }
        IEnumerable<Product> CategoryProductList { set; }
        IEnumerable<Category> CategoryList { set; }
    }

然后再创建CategoryProductsPresenter,他与HomePagePresenter相似:从ProductService中获取到的分类商品来更新视图,但他稍有不同,他要求视图提供CategoryId:

public class CategoryProductsPresenter : ICategoryProductsPresenter
    {
        private readonly ICategoryProductsView _view;
        private readonly ProductService _productService;

        public CategoryProductsPresenter(ICategoryProductsView view, ProductService productService)
        {
            _view = view;
            _productService = productService;
        }

        public void Display()
        {
            _view.CategoryProductList = _productService.GetAllProductsIn(_view.CategoryId);
            _view.Category = _productService.GetCategoryBy(_view.CategoryId);
            _view.CategoryList = _productService.GetAllCategories();
        }
    }

接下来我们还要创建下一个视图用来表示Product的详细视图,该视图显示有关特定商品的详细信息并可以添加到购物车中(Session),在该视图之前我们还需要创建一些支撑类:

public interface IBasket
    {
        IEnumerable<Product> Items { get; }
        void Add(Product product);
    }

    public class WebBasket : IBasket
    {
        public IEnumerable<Model.Product> Items
        {
            get { return GetBasketProducts(); }
        }

        public void Add(Model.Product product)
        {
            IList<Product> products = GetBasketProducts();
            products.Add(product);
        }

        private IList<Product> GetBasketProducts()
        {
            var products = HttpContext.Current.Session["Basket"] as IList<Product>;
            if (products == null)
            {
                products = new List<Product>();
                HttpContext.Current.Session["Basket"] = products;
            }
            return products;
        }
    }

WebBasket类简单的使用当前会话来存放和检索商品集合,接着我们在添加一个Navigation,用来重定向:

public enum PageDirectory { Basket }
public interface IPageNavigator { void NaviagateTo(PageDirectory page); }

实现IPageNavigator:

  public void NaviagateTo(PageDirectory page)
{
   switch (page)
  {
      case PageDirectory.Basket: 
      HttpContext.Current.Response.Redirect("/Views/Basket/Basket.aspx"); 
      break; 
      default: break;

  }

}

编写好辅助类之后,我们在创建商品详细视图,这儿需要注意一下ProduceId这个属性,和之前一样也是只读的,通过QueryString得到ProductId:

public interface IProductDetailView
    {
        int ProductId { get; }
        string Name { set; }
        decimal Price { set; }
        string Description { set; }
        IEnumerable<Category> CategoryList { set; }
    }

接下来,添加一个相应的ProductDetailPresenter(实现IProductDetailPresenter接口):

public class ProductDetailPresenter : IProductDetailPresenter
    {
        private readonly IProductDetailView _view;
        private readonly ProductService _productService;
        private readonly IBasket _basket;
        private readonly IPageNavigator _pageNavigator;

        public ProductDetailPresenter(IProductDetailView view, ProductService productService, IBasket basket,
                                      IPageNavigator pageNavigator)
        {
            _view = view;
            _productService = productService;
            _basket = basket;
            _pageNavigator = pageNavigator;
        }

        public void Display()
        {
            Product product = _productService.GetProductBy(_view.ProductId);
            _view.Name = product.Name;
            _view.Description = product.Description;
            _view.Price = product.Price;
            _view.CategoryList = _productService.GetAllCategories();
        }

        public void AddProductToBasketAndShowBasketPage()
        {
            Product product = _productService.GetProductBy(_view.ProductId);
            _basket.Add(product);
            _pageNavigator.NaviagateTo(PageDirectory.Basket);
        }
    }

最后添加购物车视图,IBasketView接口显示顾客的购物车中的所有商品以及一个用户商品目录导航的商品分类列表:

public interface IBasketView
    {
        IEnumerable<Category> CategoryList { set; }
        IEnumerable<Product> BasketItems { set; }
    }

相信接下来你已经驾轻就熟了,创建BasketPresenter,用来控制Model和View之间的数据交互:

 public class BasketPresenter : IBasketPresenter
    {
        private readonly IBasketView _view;
        private readonly ProductService _productService;
        private readonly IBasket _basket;

        public BasketPresenter(IBasketView view, ProductService productService, IBasket basket)
        {
            _view = view;
            _productService = productService;
            _basket = basket;
        }

        public void Display()
        {
            _view.BasketItems = _basket.Items;
            _view.CategoryList = _productService.GetAllCategories();
        }
    }

这样我们就完成了Presenter项目,接下来我们就可以关注视图实现了,由于篇幅有限,我挑选一个典型模块分析,具体代码可以在此下载:

MVP实现关注点的分离,集中管理相关的逻辑,View关注与UI交互,Model关注与业务逻辑,Presenter协调管理View和Model,是整个体系的核心。Model与View无关,具有极大复用性。  MVP通过将将主要的逻辑局限于Presenter,是它们具有更好的可测试性。至于并行开发,个人觉得在真正的开发中,意义到不是很大,现在开发这大多是多面手,呵! Presenter通过接口调用View降低了Presenter对View的依赖,但是View依然可以调用Presenter,从而导致了很多开发人员将Presenter当成了一个Proxy,所以我们的目的是降低View对Presenter的依赖。 “所以我更倾向于View并不知道按钮点击后回发生什么事,如Update数据,但是点击后界面有什么光线,水纹,这个应该是View关心的,View应该更注重的是和用户交互的反应。”着正是本文的观点:View仅仅将请求递交给Presenter,Presenter在适当的时候来驱动View!

View:

为了使布局统一和减少冗余代码,我们将创建Master Page和User Control:

CategoryList.ascx,用来显示所有的目录集合:

为了能让Presenter为他绑定数据,我们需要创建一个方法:

public partial class CategoryList : System.Web.UI.UserControl 
    { 
        public void SetCategoriesToDisplay(IEnumerable<Category> categories) 
        {
            this.rptCategoryList.DataSource = categories; this.rptCategoryList.DataBind(); 
        }
    }

接下来,再添加一个用户控件ProductList.ascx,用来显示商品的集合:

public partial class ProductList : System.Web.UI.UserControl 
    { 
        public void SetProductsToDisplay(IEnumerable<Model.Product> products) 
        { 
            this.rptProducts.DataSource = products; this.rptProducts.DataBind(); 
        }
    }

最后再添加一个Master Page,并为其添加一个:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Shop.master.cs" Inherits="Eyes.MVP.UI.Web.Views.Shared.Shop" %>
<%@ Register src="~/Views/Shared/CategoryList.ascx" tagname="CategoryList" tagprefix="uc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<table width="70%">
<tr>
<td colspan="3"><h2><a href="http://archive.cnblogs.com/Views/Home/Index.aspx" target="_blank" rel="nofollow">
public partial class Shop : System.Web.UI.MasterPage
{
    public CategoryList CategoryListControl
    {
        get { return this.CategoryList1; }
    }
}    

接下来添加一张ProductDetail.aspx页面,因为比较典型,所以我选他来作为分析,让其实现IProductDetailView接口:

public partial class ProductDetail : System.Web.UI.Page, IProductDetailView
    {
        private IProductDetailPresenter _presenter;

        protected void Page_Init(object sender, EventArgs e)
        {
            _presenter = new ProductDetailPresenter(this, ObjectFactory.GetInstance<ProductService>(),
                                                    ObjectFactory.GetInstance<IBasket>(),
                                                    ObjectFactory.GetInstance<IPageNavigator>());
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            _presenter.Display();
        }

        public int ProductId
        {
            get { return int.Parse(Request.QueryString["ProductId"]); }
        }

        public string Name
        {
            set { litName.Text = value; }
        }

        public decimal Price
        {
            set { litPrice.Text = string.Format("{0:C}", value); }
        }

        public string Description
        {
            set { litDescription.Text = value; }
        }

        public IEnumerable<Model.Category> CategoryList
        {
            set
            {
                Shop shopMaster = (Shop) Page.Master;
                shopMaster.CategoryListControl.SetCategoriesToDisplay(value);
            }
        }

        protected void btnAddToBasket_Click(object sender, EventArgs e)
        {
            _presenter.AddProductToBasketAndShowBasketPage();
        }
    }

这里我想提一下Ioc容器:StructureMap

Ioc

传统的控制流,从客户端创建服务时(new xxxService()),必须指定一个特定服务实现(并且对服务的程序集添加引用),Ioc容器所做的就是完全将这种关系倒置过来(倒置给Ioc容器),将服务注入到客户端代码中,这是一种推得方式(依赖注入)。术语”控制反转“,即客户放弃代码的控制,将其交给Ioc容器,也就是将控制从客户端代码倒置给容器,所以又有人称作好莱坞原则”不要打电话过来,我们打给你“。实际上,Ioc就是使用Ioc容器将传统的控制流(客户端创建服务)倒置过来,将服务注入到客户端代码中。 总之一句话,客户端代码能够只依赖接口或者抽象类或基类或其他,而不关心运行时由谁来提供具体实现。 使用Ioc容器如StructureMap,首先配置依赖关系(即当向Ioc容器询问特定的类型时将返回一个具体的实现),所以这又叫依赖注入:

 public class BootStrapper
    {
        public static void ConfigureDependencies()
        {
            ObjectFactory.Initialize(x => { x.AddRegistry<ControllerRegistry>(); });
        }

        public class ControllerRegistry : Registry
        {
            public ControllerRegistry()
            {
                ForRequestedType<ICategoryRepository>().TheDefault.Is.OfConcreteType<CategoryRepository>();
                ForRequestedType<IProductRepository>().TheDefault.Is.OfConcreteType<ProductRepository>();
                ForRequestedType<IPageNavigator>().TheDefault.Is.OfConcreteType<PageNavigator>();
                ForRequestedType<IBasket>().TheDefault.Is.OfConcreteType<WebBasket>();
            }
        }
    }

通常我们希望在启动是配置依赖关系,一般在Application_Start事件中调用ConfigureStructureMap方法:

protected void Application_Start(object sender, EventArgs e)  {         BootStrapper.ConfigureDependencies();
 }

小结

MVP设计模式我匆忙总结了一下,由于经验不足,不足之处,还望多多指点。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jeremy的技术点滴

开发小技巧备忘

34570
来自专栏葡萄城控件技术团队

Wijmo 更优美的jQuery UI部件集:服务器端Grid魔法

在之前的一篇文章中,我们讨论了如何添加一大堆的超赞的功能到一个标准的HTML表,并把它转换成一个“grid”表格。今天我想要做的事情是向你展示如何将这些功能转到...

22160
来自专栏魏琼东

一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-UDA中处理事务

前文回顾         在之前的文章一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-使用UDA操纵SQL语句和一步一步教你使用Agile...

242100
来自专栏王磊的博客

iBatis for net 框架使用

简介:ibatis 一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目,到后面发展的版本...

357100
来自专栏JackieZheng

Java豆瓣电影爬虫——模拟登录的前世今生与验证码的爱恨情仇

前言 并不是所有的网站都能够敞开心扉让你看个透彻,它们总要给你出些难题让你觉得有些东西是来之不易的,往往,这也更加激发你的激情和斗志! 从《为了媳妇的一张号,...

49060
来自专栏程序员的SOD蜜

一行代码调用实现带字段选取+条件判断+排序+分页功能的增强ORM框架

问题:3行代码 PDF.NET 是一个开源的数据开发框架,它的特点是简单、轻量、快速,易上手,而且是一个注释完善的国产开发框架,受到不少朋友的欢迎,也在我们公...

31690
来自专栏ASP.NET MVC5 后台权限管理系统

已经重写,源码和文章请跳转http://www.cnblogs.com/ymnets/p/5621706.html

文章由于写得比较仓促 已经重写,源码和文章请跳转 http://www.cnblogs.com/ymnets/p/5621706.html  前言: 导入导出实...

31680
来自专栏晓晨的专栏

.NET Core 实现定时抓取博客园首页文章信息并发送到邮箱

大家好,我是晓晨。许久没有更新博客了,今天给大家带来一篇干货型文章,一个每隔5分钟抓取博客园首页文章信息并在第二天的上午9点发送到你的邮箱的小工具。比如我在20...

33480
来自专栏跟着阿笨一起玩NET

一步一步学Linq to sql(七):并发与事务

为了看起来清晰,我已经事先把所有分类为1产品的价格和库存修改为相同值了。然后执行下面的程序:

7320
来自专栏智能大石头

【SmartOS】轻量级多任务调度系统

SmartOS是一个完全由新生命团队设计的嵌入式操作系统,主要应用于智能家居、物联网、工业自动化控制等领域。 ARM Cortex-M系列微处理器几乎全都做成...

432110

扫码关注云+社区

领取腾讯云代金券