探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现
对于SharePoint Developers来说,往往会过多的去关注SharePoint平台和工具,而把设计模式和代码的可测试性放在了一个较低的优先级。这并不是说SharePoint Developers对设计模式不感兴趣,而是缺乏在SharePoint平台下使用设计模式的经验。所以本篇Blog正如题目所示:探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现。利用MVP设计模式,可以尽量让我们的项目分离关注点、易测试、可重用。在实现MVP时,我也会加入Repository和Service Locator这两种设计模式,Repository可以理解为一个仓储,相当于数据访问层(DAL),而Service Locator扮演了IoC角色,IoC类似一个工厂(容器),工厂内部注册了很多依赖关系,IoC容器正式使用这种依赖关系从而动态的注入(又称依赖注入)提供你所需要的实例,这样可以有效的实现解耦,即分离关注点。
MVP模式
在SharePoint平台下,如开发SharePoint Farm Solution,如果不对代码进行重构,往往会出现这样的代码:
很明显这样把所有的逻辑都杂揉在UI Logic,特别是在团队开发时,即不利于测试,也不利于分工协作。而且对于SharePoint而言,开发机性能若低,调试是苦不堪言的,其耗时难以想象。所以前期如能通过单元测试解决Bug,将大大的节约时间。幸运的是,MVP设计模式的出现,对于Web Part的开发,是非常适合的。MVP的特点是很好的分离了关注点,各司其职。把上图稍作更改如下所示:
可以看到的是UI Logic处理的业务逻辑交给了Presenter,而UI彻底解放了,只单纯的做显示层(View)。
Repository Design Pattern
从上图可以看出,Presenter并不是直接去访问SharePoint数据层( SharePoint List),而是通过了一个Repository 去间接访问,而Repository Model 封装了数据层。
到这一步,看似完美,但实则还是在原地踏步。因为Presenter和Repository还是紧耦合着,这就好像负责Presenter的 A程序员必须要等负责Repository 的B程序员完成才能工作。
谁叫他们紧耦合在一起呢?
在团队开发中,我们需要的是互相独立,所以需要让负责Presenter的程序员可以使用MockRepository来做测试,这样就不会影响进度了,幸运的是,基于接口的设计,可以让我完成这个愿景。具体的实现如下:
SharePoint Service Locator Design Pattern
仔细分析上图,Presenter还是没有解耦,因为这必须要在Presenter中把某个Repository的实例创建出来,所以Presenter还是依赖了Repository这个项目程序集。这对测试没有好处,(正如前面所分析的那样,开发Presenter 的A程序员必须可以在单元测试里使用MockRepository来测试,而在真实的项目里使用B 程序员开发的AnyRepository)。
那么有没有一种方式能彻底将Presenter和Repository解耦呢?
当然有,如依赖注入,本篇博客介绍的是由Microsoft Patterns and Practices 专门为SharePoint开发的IoC容器:SharePoint Service Locator。
什么是IoC容器
传统的控制流,从客户端创建服务时,必须指定一个特定服务实现(并且对服务的程序集添加引用),IoC容器所做的就是完全将这种关系倒置过来(倒置给IoC容器),将服务注入到客户端代码中,这是一种推得方式(依赖注入)。术语"控制反转",即客户放弃代码的控制,将其交给IoC容器,也就是将控制从客户端代码倒置给容器,所以又有人称作好莱坞原则"不要打电话过来,我们打给你"。实际上,IoC就是使用IoC容器将传统的控制流(客户端创建服务)倒置过来,将服务注入到客户端代码中。
总之一句话,客户端代码能够只依赖接口或者抽象类或基类或其他,而不关心运行时由谁来提供具体实现。
使用IoC容器如SharePoint Service Locator,首先配置依赖关系(即当向Ioc容器询问特定的类型时将返回一个具体的实现),所以这又叫依赖注入。
MVP在项目中的实践
有了上面的分析,那么就来设计漂亮的代码:
- 模块化代码
- 松耦合,无依赖
- 代码重用
- 独立的单元测试
- 首先创建IVew,单纯的给UI界面"取"数据和"显示"数据
public interface IEmployeeView
{
string Country { get; }
IEnumerable<EmployeeModel> EmplyeeList { set; }
bool NotEmployeesFoundMessageVisible { set; }
}
- 接着WebPart实现IView
[ToolboxItemAttribute(false)]
public partial class VisualWebPart1 : WebPart,IEmployeeView
{
// Uncomment the following SecurityPermission attribute only when doing Performance Profiling on a farm solution
// using the Instrumentation method, and then remove the SecurityPermission attribute when the code is ready
// for production. Because the SecurityPermission attribute bypasses the security check for callers of
// your constructor, it's not recommended for production purposes.
// [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Assert, UnmanagedCode = true)]
private EmployeePresenter _presenter;
public VisualWebPart1()
{
IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(SPContext.Current.Site);
IEmployeeRepository employeeRepository = serviceLocator.GetInstance<IEmployeeRepository>();
_presenter = new EmployeePresenter(this, employeeRepository);
} protected override void OnInit(EventArgs e)
{
base.OnInit(e);
InitializeControl();
} protected void Page_Load(object sender, EventArgs e)
{
_presenter.GetEmployees();
} public string Country
{
get { return HttpContext.Current.Request["country"] }
} public IEnumerable<Model.EmployeeModel> EmplyeeList
{
set
{
rptDataSource.DataSource = value;
rptDataSource.DataBind();
}
} public bool NotEmployeesFoundMessageVisible
{
set { lblMessage.Visible = value; }
}
}
- 接着对BaseRepository的设计
public abstract class BaseRepository<T>
{
protected SPWeb _web;
public BaseRepository()
{ }
public BaseRepository(SPWeb web)
{
_web = web;
}
protected IEnumerable<T> GetEntities(SPListItemCollection items)
{
List<T> list =null;
if (items.Count>)
{
list = new List<T>();
foreach (SPListItem item in items)
{
list.Add(GetEntity(item));
}
} return list;
}
protected abstract T GetEntity(SPListItem item);
}
- 正如前面分析的那样,基于接口的设计能更好的做单元测试,所以创建IRepository
public interface IEmployeeRepository
{
IEnumerable<EmployeeModel> GetEmployeeByCountry(string country);
}
- 实现Repository
public class EmployeeRepository:BaseRepository<EmployeeModel>,IEmployeeRepository
{
public EmployeeRepository():base()
{ }
public EmployeeRepository(SPWeb web):base(web)
{ }
public IEnumerable<EmployeeModel> GetEmployeeByCountry(string country)
{
SPWeb web = _web ?? SPContext.Current.Web;
SPList list = web.Lists.TryGetList("Employee");
IEnumerable<EmployeeModel> employeeEntitiesList = null;
if (list!=null)
{
SPQuery query = new SPQuery();
query.ViewFields = string.Concat("<FieldRef Name='Title'/>", "<FieldRef Name='CountryField'/>");
query.ViewFieldsOnly = true;
if (!string.IsNullOrEmpty(country))
{
query.Query = @"<Where>
<Eq>
<FieldRef Name='CountryField'/>
<Value Type='Lookup'>" + country + @"</Value>
</Eq>
</Where>";
}
else
{
query.Query = "";
} SPListItemCollection employeeListColl = list.GetItems(query);
employeeEntitiesList = GetEntities(employeeListColl); }
return employeeEntitiesList;
}
protected override EmployeeModel GetEntity(SPListItem item)
{ return new EmployeeModel() {
Name = item["Title"].ToString(),
Country = item["CountryField"].ToString()
};
}
}
- 因为Presenter与Repository彻底解耦,故在Presenter中,根据构造函数动态注入View和Repository
public class EmployeePresenter
{
private IEmployeeView _view;
private IEmployeeRepository _repository;
public EmployeePresenter(IEmployeeView view,IEmployeeRepository repository)
{
_view = view;
_repository = repository;
}
public void GetEmployees()
{
string country= _view.Country;
if (string.IsNullOrEmpty(country))
{
return;
} var employees = _repository.GetEmployeeByCountry(country);
if (HasEmployeeFound(employees))
{
ShowEmployees(employees);
}
else
{
ShowEmployeeNotFoundMessage();
} }
private void ShowEmployees(IEnumerable<EmployeeModel> employees)
{
_view.EmplyeeList = employees;
_view.NotEmployeesFoundMessageVisible = false;
}
private void ShowEmployeeNotFoundMessage()
{
_view.NotEmployeesFoundMessageVisible = true;
}
private bool HasEmployeeFound(IEnumerable<EmployeeModel> employees)
{
if (employees!=null)
{
return employees.Count() > 0;
}
return false;
}
}
- 关键点来了,在Feature中向SharePoint Service Locator依赖注册(IRepositoy/Repositoy)
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(site);
IServiceLocatorConfig serviceLocatorConfig = serviceLocator.GetInstance<IServiceLocatorConfig>();
serviceLocatorConfig.Site = site;
serviceLocatorConfig.RegisterTypeMapping<IEmployeeRepository, EmployeeRepository>();
} //Uncomment the method below to handle the event raised before a feature is deactivated. public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(site);
IServiceLocatorConfig serviceLocatorConfig = serviceLocator.GetInstance<IServiceLocatorConfig>();
serviceLocatorConfig.Site = site;
serviceLocatorConfig.RemoveTypeMappings<IEmployeeRepository>();
}
- 注意这个Feature 的Scope必须在在Site Level之上(建议在Farm),因为有可能用户在有权限Deactivate Feature
- 根据依赖关系动态获取实例
private EmployeePresenter _presenter;
public VisualWebPart1()
{
IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(SPContext.Current.Site);
IEmployeeRepository employeeRepository = serviceLocator.GetInstance<IEmployeeRepository>();
_presenter = new EmployeePresenter(this, employeeRepository);
}
总结
至此,探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现,已经全部结束了,在这个基础架构上还可以继续优化,如DataMapper等。相信构建高效清晰整洁的代码是每个程序员所追求的,你不得不佩服国外大神们总结的设计模式是多么的精妙,或许怀着敬畏的心才能慢慢体会其中的奥秘。点击此处下载源代码
探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现的更多相关文章
- PyQt学习问题:Model/View中中EditKeyPressed常量平台编辑键(the platform edit key )是什么?
老猿在学习PyQt的Model/View设计时,发现是否允许对视图中的数据项进行编辑的函数setEditTriggers的参数QAbstractItemView.EditTriggers是几个常量的组 ...
- 5.Qt model view设计模式
Introduction to Model/View Programming QT4 介绍了一系列新的 Item View 类,这些类使用Model/View结构来管理数据和数据如何呈现给 ...
- 第十四章、Model/View开发:Model/View架构程序设计模式
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.简介 在PyQt和Qt中,Model/View架构是图形界面开发时用于管理数据和界面展现方式的关 ...
- MVC(Model View Controller)框架
MVC框架 同义词 MVC一般指MVC框架 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一 ...
- Android -------- MVC,MVP 和 MVVM 架构设计模式
MVC(Model-View-Controller)是最常见的软件架构之一,业界有着广泛应用.它本身很容易理解,但是要讲清楚,它与衍生的 MVP 和 MVVM 架构的区别就不容易了. 一.MVC MV ...
- 第15.22节 PyQt(Python+Qt)入门学习:Model/View架构详解
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.简介 在PyQt和Qt中,Model/View架构是图形界面开发时用于管理数据和界面展现方式的关 ...
- Qt Model/View(官方翻译,图文并茂)
http://doc.trolltech.com/main-snapshot/model-view-programming.html 介绍 Qt 4推出了一组新的item view类,它们使用mode ...
- (转)Qt Model/View 学习笔记 (一)——Qt Model/View模式简介
Qt Model/View模式简介 Qt 4推出了一组新的item view类,它们使用model/view结构来管理数据与表示层的关系.这种结构带来的 功能上的分离给了开发人员更大的弹性来定制数据项 ...
- Qt的Model/View Framework解析(数据是从真正的“肉(raw)”里取得,Model提供肉,所以读写文件、操作数据库、网络通讯等一系列与数据打交道的工作就在model中做了)
最近在看Qt的Model/View Framework,在网上搜了搜,好像中文的除了几篇翻译没有什么有价值的文章.E文的除了Qt的官方介绍,其它文章也很少.看到一个老外在blog中写道Model/Vi ...
随机推荐
- php理解变量的作用域
作用域是指在一个脚本中某个变量可以使用或可见的范围,php具有6项基本的作用域规则. 1.内置超级全局变量可以在脚本的任何地方使用和可见. 2.常量,一旦被声明,将可以在全局可见:也就是说,它们在函数 ...
- Tomcat8配置进入管理端
1:修改tomcat-users.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> <tomcat ...
- Java 判断Windows下某个进程是否运行
public static void main(String[] args) { String keyWord = "chrome.exe"; Runtime runtime = ...
- console.time 简单分析javascript动态加入Dom节点的性能
Bullshit 本来想每天都更新下博客的,可是近期要考试,还有就是自己还是停留在暗自窃喜中吧(这样的想法要改变). 事实上近期总在想.自己要怎么去管理自己的数据,每天的生活都是对自己的数据的增删查改 ...
- Pig拒绝连接错误
运行Pig时出现错误: Pig Stack Trace --------------- ERROR 1066: Unable to open iterator for alias visit. Bac ...
- createjs入门
createjs是一个轻量级的框架,稍微有点时间和耐心,就可以把全部源代码都看一遍,毕竟只有三十几个js文件.地址:http://www.createjs.com/ 开发createjs的动画或游戏, ...
- Java RSA (SHA1withRSA)签名和验签
static { try { SIGNATURE = Signature.getInstance("SHA1withRSA", "BC"); } catch ( ...
- 〖Linux〗Ubuntu14.04安装32位运行库
在终端操作: sudo dpkg --add-architecture i386 echo "deb http://old-releases.ubuntu.com/ubuntu/ rarin ...
- 〖Android〗sshd for android, 及映射根文件系统至本地盘符
严重问题: 若移植失败将可能直接导致手机***无法开机***,导入相关文件需慎重! 达成效果: 1. ssh 远程登录 Android 终端: 2. sftp 挂载/映射 Android 根文件系统至 ...
- MVC5为WebAPI添加命名空间的支持1
前言 默认情况下,微软提供的MVC框架模板中,WebAPI路由是不支持Namespace参数的.这导致一些比较大型的项目,无法把WebApi分离到单独的类库中. 本文将提供解决该问题的方案. 微软官方 ...