Thinking In Design Pattern——MVP模式演绎
原文《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设计模式我匆忙总结了一下,由于经验不足,不足之处,还望多多指点。
Thinking In Design Pattern——MVP模式演绎的更多相关文章
- Design Pattern - 命令模式
一般执行一个操作的过程, 创建对象, 并调用对象的函数, 函数执行, 返回 比如下面的类图, client直接调用Receiver.action 而命令模式, 抽象出command对象, 并在comm ...
- Design Pattern - 访问者模式
访问者模式 访问者模式(Visitor), 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 这个模式相对比较复杂, 而又很少能被用上, 拿G ...
- Scalaz(10)- Monad:就是一种函数式编程模式-a design pattern
Monad typeclass不是一种类型,而是一种程序设计模式(design pattern),是泛函编程中最重要的编程概念,因而很多行内人把FP又称为Monadic Programming.这其中 ...
- 用户登录(Material Design + Data-Binding + MVP架构模式)实现
转载请注明出处: http://www.cnblogs.com/cnwutianhao/p/6772759.html MVP架构模式 大家都不陌生,Google 也给出过相应的参考 Sample, 但 ...
- [Design Pattern] Command Pattern 命令模式
发现公司的代码好像有用到 Command Pattern,回顾重温下. Command Pattern 的类图结构如下: 参考 <Head First Design Patterns(英文版)& ...
- Android MVP模式
转自http://segmentfault.com/blogs,转载请注明出处Android MVP Pattern Android MVP模式\[1\]也不是什么新鲜的东西了,我在自己的项目里也普遍 ...
- Android MVP模式 谷歌官方代码解读
Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...
- Laravel与Repository Pattern(仓库模式)
为什么要学习Repository Pattern(仓库模式) Repository 模式主要思想是建立一个数据操作代理层,把controller里的数据操作剥离出来,这样做有几个好处: 把数据处理逻辑 ...
- 在Andoid开发中使用MVP模式来解耦,增加可测试性
by Jeff Angelini posted on 7/20/2011 2:35:00 PM 将应用程序UI的表现从Ui的逻辑中分离是一个好的想法.这种分离减少了代码耦合,代码更加干净, 甚至可以有 ...
随机推荐
- Oracle数据库imp
创建表空间 create tablespace ICITY datafile 'D:\Oracle\oradata\orcl\ICITY.DBF' size 400M autoextend on ne ...
- SQL还原后:目录名称无效
使用Sql Server备份文件,还原数据库出现如下错误:目录名称无效 解决方法:在系统临时文件夹内,如C:\Users\Administrator\AppData\Local\Temp\ 下新建名称 ...
- Vue.js 2.0 独立构建和运行时构建的区别
Vue.js 2.0 独立构建和运行时构建的区别 在使用 Vue.js 2.0 时,有独立构建(standalone)和运行时构建(runtime-only)两种版本可供选择.而在 Vue.js 1. ...
- MongoDB内存管理机制
目前,MongoDB使用的是内存映射存储引擎,它会把磁盘IO操作转换成内存操作,如果是读操作,内存中的数据起到缓存的作用,如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,总之可以大幅度提升性 ...
- Android 建立手机与手表数据同步机制总结
Android Wear 数据同步机制总结 当手机与手表建立蓝牙连接之后.数据就能够通过Google Play Service进行传输. 同步数据对象Data Item DataItem提供手机与手表 ...
- MySQL参数化查询的IN 和 LIKE
https://stackoverflow.com/questions/650455/c-sharp-parameterized-query-mysql-with-in-clausehttps://s ...
- php函数method_exists() 与is_callable()区别
php函数method_exists()与is_callable()的区别在哪?在php面相对象设计过程中,往往我们需要在调用某一个方法是否属于某一个类的时候做出判断,常用的方法有method_exi ...
- DirectX中文手册
目 录 第一章 DirectX基础(初级篇) 第一节 什么是DirectX 一.什么是DirectX ? 二.DirectX的组成部分 三.关于DirectDraw 四.为什么要使用DirectD ...
- 点击threadItem查看MessageList时传递数据
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ...
- uitableview做九宫格
1:创建实体 #import <Foundation/Foundation.h> @interface Shop : NSObject @property (nonatomic, copy ...