經過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類同樣。

總結

在這章中,你已經看到了你如何處理一些公共需求在企業應用中好比可維護性和測試性經過爲你的應用程序採起一個鬆散耦合設計。

相關文章
相關標籤/搜索