探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现

摘要:
它缺乏在SharePoint平台下使用设计模式的经验。探索MVP(模型视图演示者)设计模式在SharePoint平台中的实现。IoC容器正式使用这种依赖关系来动态注入(也称为依赖注入),以提供所需的实例。演示者不直接访问SharePoint数据层(SharePointList)。
探索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,如果不对代码进行重构,往往会出现这样的代码:

探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现第1张

很明显这样把所有的逻辑都杂揉在UI Logic,特别是在团队开发时,即不利于测试,也不利于分工协作。而且对于SharePoint而言,开发机性能若低,调试是苦不堪言的,其耗时难以想象。所以前期如能通过单元测试解决Bug,将大大的节约时间。幸运的是,MVP设计模式的出现,对于Web Part的开发,是非常适合的。MVP的特点是很好的分离了关注点,各司其职。把上图稍作更改如下所示:

探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现第2张

可以看到的是UI Logic处理的业务逻辑交给了Presenter,而UI彻底解放了,只单纯的做显示层(View)。

Repository Design Pattern

从上图可以看出,Presenter并不是直接去访问SharePoint数据层( SharePoint List),而是通过了一个Repository 去间接访问,而Repository Model 封装了数据层。

探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现第3张

到这一步,看似完美,但实则还是在原地踏步。因为Presenter和Repository还是紧耦合着,这就好像负责Presenter的 A程序员必须要等负责Repository 的B程序员完成才能工作。

谁叫他们紧耦合在一起呢?

在团队开发中,我们需要的是互相独立,所以需要让负责Presenter的程序员可以使用MockRepository来做测试,这样就不会影响进度了,幸运的是,基于接口的设计,可以让我完成这个愿景。具体的实现如下:

探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现第4张

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[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>0)
            {
                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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
  • 关键点来了,在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平台下的实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Docker容器数据卷[4]koa和egg项目webpack热更新实现下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

如何才能学到Qt的精髓——信号槽之间的无关性,提供了绝佳的对象间通讯方式,QT的GUI全是自己的一套,并且完全开源,提供了一个绝好机会窥视gui具体实现

姚冬,中老年程序员 叶韵、KY Xu、赵奋强 等人赞同 被邀请了很久了,一直在思考,今天终于下决心开始写回答。这个问题的确是够大的,Qt的代码规模在整个开源世界里也是名列前茅的,这么大的项目其中的精华是非常多的,很难说得全面,实际上我对Qt也不是完全了解,里面还有很多我不熟悉的东西。首先,我想谈的是 signal/slot,Qt算是发明了signa...

提高github下载速度的方法【100%有效】可达到2MB/s

转:https://blog.csdn.net/kcx64/article/details/83866633 在国内从github上面下载代码的速度峰值通常都是20kB/s。这种速度对于那些小项目还好,而对于大一些的并且带有很多子模块的项目来讲就跟耽误时间。虽然有很多提速的方法,但是实际用起来并不稳定。这里提供一种新的方法,下载速度可以达到 1~2MB/s...

ASP.NET(C#)实现一次性上传多张图片(多个文件)

在做asp.net的Web开发的时候,我们经常会遇到一次性上传多个文件的需求。通常我们的解决方法是固定放多个上传文件框,这样的解决办法显然是不合理的,因为一次上传多个,就意味着数量不确定。因此我们就要让这些文件上传框动态添加,下面我以我做的一个图库管理中的上传图片的功能为例 先看效果: 打开的初始界面: 默认是上传一个图片,但当我们点“增加图片”按钮时可以...

软工作业05

软件工程 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1 作业要求 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10619 作业目标 网页实现家族树 作...

Ubuntu下添加开机启动项的2种方法

1、方法一,编辑rc.loacl脚本 Ubuntu开机之后会执行/etc/rc.local文件中的脚本,所以我们可以直接在/etc/rc.local中添加启动脚本。当然要添加到语句:exit 0 前面才行。如: 复制代码 代码如下: sudo vi /etc/rc.local 然后在 exit 0 前面添加好脚本代码。 2、方法二,添加一个Ubuntu的...

初学爬虫之访问goole网页与爬取中国大学排名。

Requests库get()函数访问google网页20次。 1.Requests模块介绍: Requests 是使用 Apache2 Licensed 许可证的 HTTP 库。用 Python 编写,真正的为人类着想。 Python 标准库中的 urllib2 模块提供了你所需要的大多数 HTTP 功能,但是它的 API 太渣了。它是为另一个时代、另一个...