经过Unity依赖注入

前言web

Unity容器的思想起始于我在为Web Client Sofitware Factory项目工做的时候,微软的patterns&practices团队已经使用依赖注入的概念好几年了在那时候,最著名的是Composite Application Block(CAB)。它也是Enterprise Library 2.0的核心配置,当咱们开始为web装备组件应用程序的时候它又一次成为了中心(一个以CWAB闻名的库)。数据库

咱们的目标一直是促进依赖注入的概念作为一个创建松散耦合系统的方式。然而,那时候p&p实现依赖注入的方式和咱们如今考虑的已经不同了。替代一个单独的重用容器,DI实现应该是一个专门的系统以使用。咱们使用一个叫作ObjectBuilder的库,它被描述为“一个创建DI容器的框架”。理论上,让咱们每一个项目写一个咱们确实须要的容器。编程

一个崇高的抱负,可是在实践中它工做的不是很好。ObjectBuilder是一个高度解耦、抽象的部分集合而且不得不手动集成。和一个缺乏文档的文件组合它将花费大量时间理解什么要去哪和怎么把它放在一块儿造成一个有用组合。时间将花费在写代码,调试和优化DI容器而不是咱们实际在项目中须要的。安全

它甚至有更多的乐趣当某我的要使用CAB(在一个ObjectBuilder版本基础上使用一个DI容器)和Enterprise Library(在不一样ObjectBduilder版本的基础上使用分离的容器)在一个项目中。集成是很困难的;仅仅在同一个项目中处理两个不一样版本的ObjectBuilder引用已是一个挑战了。还要把一次性容器导向一次性扩张和集成接口:在Enterprise Library中工做的在CAB无用,反之亦然。app

紧要关头来了,当咱们又花费了一周在Web Client Software Factory项目快结束的时候修复一堆在CWAB里的bugs:这些bugs很像咱们以前在CAB中修复过的。它不能够再好一点吗,咱们提出问题,咱们是否能够只有一个容器实现而且用它替代一遍又一遍的写容器?框架

在这个基础下构建Unity。Enterprise Library 4.0团队把依赖注入Application Block(开始的时候,做为Unity使用而被你们认知)放入产品订单中。咱们为了项目的目标是明确的。首先,介绍和推销依赖注入的概念给咱们的社区,没有太多底层实现细节的阻碍。其次,提供一个核心容器和简单使用的API那样咱们,微软的其它团队,或者谁使用开源项目很差用的均可以使用。第三,有一个多样性扩张机制那么新的特性能够被任何人添加进来而不须要拆开核心代码。单元测试

在我看来Unity由于这些目标而得到成功。我特别自豪咱们如何影响了.NET开发者社区。Unity在.NET生态系统中快速变成了一个最经常使用的DI容器。更重要的,其它的DI容器使用也增长了。Unity介绍DI给一些历来没有据说过DI的人群。这些人中的一些人不久就转移到了他们须要的其它容器。那不是Unity的损失:这些人使用Unity的概念,那是重要的部分。学习

没有更多的关于DI容器的福音能够公布了。在我看来,这是由于DI再也不是一个“专家技术”:它如今是主流的一部分。当微软(特别是ASP.NET MVC和WebAPI)来的framworks支持DI内建的时候,你知道一个理念已经到达了核心听众。我认为Unity在这个事件中起到了很大的做用。测试

我很兴奋能够看到这本书发布。第一次,有一个地方你能够同时查找DI的概念和怎么使用Unity容器应用这些概念,而且有一个扩张故事的覆盖率,有一些事情我一直要写可是没有开始。我不再用感到愧疚了。优化

读这本书,拥抱概念,享受松散耦合,高内聚软件,DI使构建这一切如此简单!

Chris Tavares

Redmond,WA,USA

April 2013


引语

关于这个指南

这个指南是一个资源可用于Unity3版本帮助你学习Unity,学习一些关于Unity问题和议题帮助你在你的应用程序中拿出和开始Unity。Unity是主要的依赖注入容器所以指南也包含一个依赖注入的介绍,即使你不计划使用Unity你也能够孤立的阅读,虽然咱们但愿你使用它。

章节的设计是顺序阅读,每个章节成立在上一个章节之上,交互的章节覆盖了一些概念背景材料,这些章节指出在你本身的应用程序中使用Unity。假如你已经熟悉依赖注入和拦截的概念,你或许能够关注第三章,“经过Unity使用依赖注入”,第五章,“经过Unity拦截”,和第六章,“Unity扩展”。

前两章介绍了概念背景和解释了依赖注入是什么,它的优势和缺点是什么,当你考虑使用它的时候。章节三而后应用这些理论性的知识,在各类各样的场景怎么使用Unity容器并提供实例和引导。

第四章描述拦截作为一个技术动态插入代码为你的应用程序提供横切支持。

第五章讨论关于interception and policy injection(拦截和策略注入)的高级主题,独自面对选择,拿出一些建议当你使用它的时候。

剩下的章节介绍一些方法,你能够扩展Unity,好比建立容器扩展或者建立自定义声明周期管理者。

这个指南同时包括几个论文,叫作Tales from the Trenches,开发者适应和自定义Unity。额外的论文可能能够从网上(http://msdn.com/unity)获得,因此确保检查他们出来。假如你要和开发者社区详尽的分享你的故事,把它发送到ourstory@microsoft.com

全部的章节包括引用附加的资源,好比书,博客,和论文将会提供额外的细节假如你想要对一些主题进行更深刻的探索。为了你的方便,有一个在线文献目录包些全部的连接因此这些资源只要点击就能阅读。你能够在http://aka.ms/unitybiblio  找到文献目录。

在这些章节中的大多实例代码来自一个应用程序集合你能够从http://unity.codeplex.com/downloads/get/683531 下载。

这个指南不包括Unity特征的每个细节信息或者在Unity程序集中的每个类。要查找信息,你应该查看https://unity.codeplex.com/downloads/get/669364 和  https://msdn.microsoft.com/en-us/library/dn170424(v=pandp.30).aspx

这本书是为谁写的

这本书打算给一些设计师,开发者,或者设计,创建,以及操做应用程序和服务的信息技术(IT)专业者和想要学习怎么使用Unity容器依赖注入容器给他的应用程序带来好处。你应该熟悉微软的.NET Framework,和微软的Visual Studio才能获得从这个指南里获得全部的好处。

1  介绍

在你学习关于依赖注入和Unity以前,你须要理解为何你应该使用它们。而且为了理解为何你应该使用它们,你应该了解依赖注入和Unity设计是为了解决什么问题。这个介绍章节不会说太多关于Unity,或者确实讲太多关于依赖注入,可是它将提供一些须要的背景信息将帮助你领会依赖注入做为一个技术带来的益处而且为何Unity为何那么作。

下一个章节,第二章,“依赖注入”,将会教你依赖注入如何帮助你符合这一章要求的概述,接下来的章节,第三章,“经过Unity实现从来注入”,说明Unity在你的应用程序中如何帮助你实现依赖注入的方法。

动机

当你设计和开发软件系统,有不少需求要考虑。一些将会指定为系统问题而一些是更常见的目的。你能够把一些需求归类为功能需求,一些作为非功能需求(或者质量属性)。全面的功能集合将会改变对应不一样的系统。如下的需求集合概述是公共需求,特别是对于行业(LOB)软件系统带有相对较长的生命周期。没有必要把你开发的每个系统都看得很重要,可是你能够确信一些系统将会在你工做的众多的项目的需求列表上。

可维护性

随着系统愈来愈大,预期的系统生命周期愈来愈长,维护这些系统变得愈来愈有挑战。常常,起始团队成员开发的系统再也不可用,或者不记得系统的详情。文档可能已通过期甚至丢失了。同时,商务可能要求转换动做以符合一些有压力的商务需求。可维护性是一个软件系统的质量决定了当你更新系统的时有多容易和多有效。你可能须要更新一个系统假如发现了一个缺陷须要修复(换句话说,执行维护保养),假如一些在操做环境的改变须要你对系统进行改变,或者假如你须要给系统添加一个新的产品特色以符合商业需求(完成维护保养)。可维护的系统提高组织的灵活性并减小开销。所以,作为你的设计目标你应该包括可维护性,和其余的好比可靠性、安全性和可扩展性。

可测试性

一个可测试的系统是指,你能够有效的测试系统单独的部分。设计和书写高效的测试用例和设计和书写可测试的应用程序代码同样是个挑战,特别随着系统变得愈来愈大和愈来愈复杂。方法论好比测试驱动开发(TDD)要求你在写任何代码实现一个新的特性以前先写单元测试,以此为目标设计技术会改善你的应用程序质量。这种设计技术同时帮助你扩大单元测试的范围,减小回溯的可能性,而且使重构变的更容易。然而,做为你的测试操做的一部分你也应该包含其它类型的测试,好比可接受性测试,集成测试,和压力测试。

运行测试会花费金钱和消耗时间由于要求在一个真实的环境测试。例如,为了一些云端的应用程序的测试,你须要把应用程序部署到云环境和在云上运行测试。假如你使用TDD,在云端一直运行全部测试可能不切实际由于部署你的应用程序时间比不是在本地模拟器上的时间花的更多。在这类状况下,为了使你能隔离的运行配套的单元测试你可能决定使用双测试(简单stubs或者可检验mocks)替代真实组件在云端实现测试在标准的TDD开发中。

测试性应该和可维护性和灵敏度一块儿做为你的系统的设计目标,一个可测试系统通常是可维护的,反之亦然。

灵活性和扩展性

灵活性和扩展性也是经常是企业应用程序清单上的理想属性。给出的商业需求常常发生变动,即便在部署应用程序期间和已经发布成产品,你应该尝试设计足够灵活的应用程序以致于它能够在不一样的途径下能够适应工做和扩展,你能够添加新的功能。例如,你可能须要把你的应用程序从本地部署装换为云端部署。

后期绑定

在一些应用程序场景下,你可能有一个支持后期绑定的需求。后期绑定颇有用假如你须要不从新编译替换你的系统的一部分的能力。例如,你的应用程序支持多个关系数据库,每一个支持的数据库类型带有一个分离的模块。你可使用一个声明配置告诉应用程序在运行时使用一个指定的模块。后期绑定其它有用的场景是使系统用户经过插件提供他们本身的定制化服务。此外,你能够指挥系统经过一个配置集合或者一个约定(系统在文件系统中扫描特别的位置以供模块使用)使用指定的定制化服务。

平行开发

当你在开发大规模(或者甚至小型和中型规模)系统,所有开发团队同时开发一个功能和组件是不现实的。事实上,你将分配不一样的功能和组件给小组让他们平行开发。即便这种方法使你减小了在项目上的期间,它也带来了额外的复杂度:你须要管理多个组并确保你能够集成不一样组开发的应用程序使之正确工做。

横切关注点

企业应用程序典型的须要处理一系列的横切关注点好比验证,异常处理,和登陆。你可能在应用程序的不少不一样区域须要这些功能而且你将要以标准化、一致的方法实现它们以提升系统的可维护性。观念上,你要一个机制使你有效的和透明的添加行为到你的对象要么在设计时要么在运行时而不须要你对已存在的类型作任何更改。一般,你须要在运行时配置这些功能的能力并在一些状况下,在存在的应用程序中添加功能放值一个新的横切关注点。

松散耦合

你能够查看前面章节列出的不少需求确保你的设计引发应用程序松散耦合,多个部分组成了应用程序。松散耦合,对立于紧耦合,意味着减小组成你的系统的组件之间依赖,由于系统的各个部分大部分独立于其它。

一个简单的实例

如下的实例阐明了紧耦合,ManagementController类型直接依赖TenantStore类型。这些类可能不一样于Visual Studio项目。

public class TenantStore
{
    ...
    public Tenant GetTenant(string tenant){
    ...
    }

    public IEumerable<string> GetTenantNames(){
    ...
    }
}

public class ManagementController{
    
    private readonly TenantStore tenantStore;
    public ManagementController(){
        tenantStore=new TenantStore(...);
    }

    public ActionResult Index(){
        var model=new TenantPageViewData<IEnumerable<string>>(this.tenantStore.GetTenantNames()){
        Title="Subscribers"
    };
    return this.View(model);
    }
    

    public ActionResult Detail(string tenant){
    var contentModel=this.tenantStore.GetTenant(tenant);
    var model=new TenantPageViewData<Tenant>(contentModel){
        Title=string.format("{0}details",contentMdoel.Name)
    };
    return this.View(model);
    
    }
    ......
}

ManagementController和TenantStore经过这个导入被用于各类各样的表单。即便ManagementController是一个ASP.NET MVC控制器,你不须要知道听从MVC。然而,这些实例意在看起来像你将在真实世界中见到的各类类型同样的,特别是在第三章的实例。

在这个例子中,TenantStore类实现了一个仓储,处理访问底层数据存储例如一个关系数据库,ManagementController是一个MVC控制器类从仓储请求数据。注意ManagementController类必需要么实例化一个TanantStore对象要么从什么地方获取一个TanentStore对象的引用,在它能够调用GetTenant和GetTenantNames方法以前。ManagementController类依赖于指定的、具体的TenantStore类型。

假如你为企业应用程序从新说起在这章开始时的公共理想需求,你能够评估在以前代码示例轮廓对你有多大帮助。

即使这个简单的示例显示只有单一客户端TenantStore类,实际上在你的应用程序中可能有多个客户端类使用TenantStore类。若是你设想每一个客户端类有一个实例负责或者在运行时定位一个TenantStore类型,那么全部的都须要改变当TenantStore类的实现改变了。这样一来维护TenantStore类可能会更复杂,更容易出错,而且消耗更多时间。

为了ManagementController类中的Index和Detail方法运行单元测试,你须要实例化一个TenantStore对象而且确保底层数据存储包含合适的测试数据以测试。这使得测试过程变得复杂,而且依赖于你使用的数据存储,运行测试可能消耗更多时间由于你必须建立和在数据存储中填入正确数据。它也使测试变得更脆弱。

使用不一样的数据存储有可能改变TenantStore类的实现,例如Windows Azure表存储替代SQL Server。无论怎么样,对于使用TenantStore实例的客户端类它可能须要一些改变若是它必须为这些客户端类提供一些初始化数据,好比链接字符串。

这个方法不能使用晚期绑定由于客户端类直接编译TenantStore类使用。

假如你须要为横切关注点添加支持好比登陆到多个存储类,包括TenantStore类,你须要独立的修改和配置每一个你的存储类。

如下代码示例显示了一个小的更改,在客户端里的ManagementController类的构造器如今接收到一个实现了ITenantStore接口的对象而且TenantStore类提供了一个相同接口的实现。

public interface ITenantStore
    {
        void Initialize();
        Tenant GetTenant(string tenant);
        IEnumerable<string> GetTenantNames();
        void SaveTenant(Tenant tenant);
        void UploadLogo(string tenant, byte[] logo);
    }

  

    public class TenantStore : ITenantStore
    {
         ...
        public TenantStore()
        {
         ....   
        }
          ...
    }

    public class ManagementController : Controller
    {
        private readonly ITenantStore tenantStore;

        public ManagementController(ITenantStore tenantStore)
        {
            this.tenantStore = tenantStore;
        }

        public ActionResult Index()
        {
            ...
        }

        public ActionResult Detail(string tenant)
        {
            ...            
        }
           ...
    }

这个改变直接影响了你能够有多简单的符合需求清单。

如今ManagementController类很干净,而且任何其余的客户端TenantStore类再也不为TenantStore实例对象负责,尽管示例代码已经显示再也不为某个类或组件实例化负责。从维护的观点看,责任属于一个类好过多个类。

如今很清楚controller依赖什么是从构造器参数来的代替埋葬在控制器方法实现内部。

为了测试一些客户端类的行为好比ManagementController类,你如今能够提供一个轻量的ITenantStore接口的实现返回一些样本数据。这替代了建立TenantStore对象查询底层数据存储取出样本数据。

引入ITenantStore接口使得它更容易替换存储实现而不须要更改客户端类由于它们所期待的是一个实现接口的对象。假如接口是在一个分离的项目实现,那么包含客户端类的项目只须要持有一个包含接口定义的项目的引用。

负责实例化存储的类如今也可能为应用程序提供一个额外的服务。它能够控制它建立的ITenantStore实例的生命周期,例如每次建立一个新的对象ManagementController客户端类须要一个实例,或者维护一个单独的实例,当一个客户端须要它的时候传递它的引用。

如今有可能使用晚期绑定了由于客户端类仅仅引用了ITenantStore接口类型。应用程序能够在运行时建立一个实现接口的对象,也许在一个配置集合的基础上,并把对象传递给客户端类。例如,应用程序可能建立配置文件,而且把配置文件传递给ManagementController类的构造器。

假如接口定义是经过协议的,两个团队能够并行工做在存储类和控制器类上。

负责建立存储类实例的类如今能够为横切关注点添加支持在传递存储实例给客户端以前,例如使用装饰模式传递一个实现了关注横切点的对象。你不须要改变客户端类或存储类添加对横切关注点的支持例如登陆或者异常处理。

在第二段代码示例显示的途径是一个松散耦合设计使用了接口。假如咱们能够在类之间移除一个直接依赖,它减小解决方案耦合的层次而且帮助增长可维护性,可测试性,灵活性和扩展性。

在第二段代码示例没有显示的是如何依赖注入而且Unity容器适应到图片,即便你可能猜到它们将会负责建立示例并传递给客户端类。第二章描述依赖注入的角色作为一个技术以支持松散耦合,而且第三章描述Unity如何帮助你在你的应用程序中实现依赖注入。

何时你应该使用一个松散耦合设计?

在咱们进一步讨论依赖注入和Unity以前,你应该开始了解在你的应用程序的什么地方应该考虑引入松散耦合,接口编程,并减小类之间的依赖。咱们在前面章节第一个需求是可维护性,这一般给出一个好的指示,什么时候何处考虑减小应用程序中的耦合。一般,越大越复杂的应用程序,它就变的越难维护,所以这些技术更有可能变的有用。当不顾应用程序的类型时这是事实:它多是一个桌面应用程序,一个网页应用程序,或者一个应用程序。

乍一看,这好像是反直觉的。上面显示的第二个示例代码引入了一个在第一个没有引入的接口,它也须要一些咱们没有显示的信息,这些信息表明客户端类负责实例化和管理对象。经过一个小示例,这些技术显现了添加解决方案的复杂度,可是随着应用程序变的愈来愈大和愈来愈复杂,这些开销会变的愈来愈不明显。

以前的示例也阐明了另外一个关于在哪里适合使用这些技术的观点。最可能的,ManagementController类存在于应用程序的用户接口层,TenantStore类是数据访问层的一部分。它是一个设计应用程序的公共方法所以在将来它可能替换一个层而不打扰其它层。例如,替换或者添加一个新的UI给应用程序(好比为手机平台建立一个app除了传统的网页UI外)而不改变数据层或者替换底层存储机制不盖被UI层。生成应用程序使用多个层帮助应用程序的各个部分解耦。你应该尝试认同应用程序的部件未来有可能改变,为了最小化、局部化这些改变带来的影响,从应用程序剩余部分分离。

前面章节列出的需求清单也包含横切关注点你可能须要在你的应用程序中以统一的方式给一系列类应用横切。示例包含的关注点经过应用程序块在企业库中处理(https://msdn.microsoft.com/library/cc467894.aspx)好比登陆,异常处理,验证,或者瞬时故障处理。 这里你须要确认你须要这这些的哪里处理横切关注点,以便为类添加了功能还不在类里存在添加的信息。这将帮助在应用程序中一致管理功能并引入一个干净的分离关注点。

面向对象设计原则

终于,在进一步讨论依赖注入和Unity以前,关于面向对象编程和设计目前为止咱们要涉及5个SOLID原则的讨论。SOLID是如下原则首字母缩略词:

单一责任原则

开放/关闭原则

Liskov替换原则

接口分离原则

依赖倒置原则

接下来的章节将描述这些规则和它们与松散耦合的关系和这章开始时列出的需求。

单一责任原则

单一责任原则声明一个类应该有一个,而且只有一个要改变的缘由。获取更多的信息,查看Robert C.Martin的面向对象设计原则的文章。

在这章开始的第一个简单示例中,,ManagementController类有两个责任:在UI中扮演一个控制器而且实例化和管理TenantStore对象的生命周期。在第二个示例中,负责实例化和管理TenantStore对象在系统的另外一个类或者组件中。

开放/关闭原则

开放/关闭原则声明“软件实体(类,模块,功能,等等)对于扩展应该开放,可是对修改关闭”(Meyer,Bertrand(1988)。面向对象软件构造。)即使你多是为了修复缺陷更改代码,你应该扩展一个类假如你要添加给它添加任何新的行为。这将帮助保持代码可维护性和可测试由于存在的行为没有改变,而且新的行为位于新的类中。给你的应用程序添加横切关注点的支持需求能最好的符合开放/关闭原则。例如,当你在你应用程序中添加登陆到一个类集合中,你不该该对已经存在的类的实现作任何更改。

Liskov替换原则

Liskov替换原则在面向对象中声明在一个电脑程序中,若是S是T的子类型,那么该程序中,T类型的对象能够被S类型的对象替换而不惊动任何知足须要的属性,好比正确性。

这章的第二段代码示例中显示,ManagementController应该能够像期待的那样继续工做当你传给它一个ITenantStore接口的实现。这个示例使用一个接口类型作为类型传递给ManagementController类的构造器,可是你一样使用一个抽象类型。

接口分离原则

接口分离原则是一个软件开发原则意在使软件更好维护。接口分离原则鼓励松散耦合,于是使系统更容易重构、更改、从新部署。原则声明很大的接口应该拆分红更小和更明确的,那么客户端只须要知道它用获得的方法:没有客户端类应该被强制依赖它用不到的方法。

在这章以前定义的ITenantStore中,假如你决定不是全部的客户端类都会使用UploadLogo方法你应该考虑分离这个方法到一个分开接口正如一下代码示例显示的那样:

public interface ITenantStore
{
    void Initialize();
    Tenant GetTenant(string tenant);
    IEnumerable<string> GetTenantNames();
    void SaveTenant(Tenant tenant);
}

public interface ITenantStoreLogo
{
    void UploadLogo(string tenant,byte[] logo);
}

public class TenantStore:ITenantStore,ITenantStoreLog
{
    ...
    public TenantStore()
    {
        ...
    }
    ...
}

依赖倒置原则

依赖倒置原则声明:

高层模块不该该依赖底层模块。两个都应该依赖抽象。

抽象不该该依赖于细节之上。细节依赖于抽象之上。

这章的两个代码示例都阐明了怎么应用这个原则。在第一个示例中,高层ManagementController类依赖于低层TenantStore类。这通常限制了高层类用于另外一个上下文的选择。

在第二个示例中,ManagementController类如今依赖于ITenantStore抽象,正如TenentStore类同样。

总结

在这章中,你已经看到了你如何处理一些公共需求在企业应用中好比可维护性和测试性经过为你的应用程序采起一个松散耦合设计。

相关文章
相关标签/搜索