本文章是由機器翻譯。

深入 Microsoft 典範與實例

程式庫中的相依性插入

Chris Tavares

重整 Microsoft 企業程式庫

相依性插入 (DI) 是已被 traction.NET 開發人員社群中的取得過去幾年的模式。 顯著的部落格作者有被談 DI 優點相當一段時間。 」 主題的幾篇文章已發佈在 的 MSDN Magazine。 .NET 4.0 將運送與計劃,以在未來成長至完整 DI 系統的某些 DI 類似的功能。

在讀取部落格文章和發行項上 DI,我注意到小型但重要的偏差之主題的涵蓋範圍。 在整個應用程式的內容中使用 DI 談論寫入器。 但您想要撰寫程式庫或使用 DI 的架構的要是嗎? 這項變更焦點不會如何影響圖樣的使用方式? 這是我們 (典範 & 實例企業程式庫小組) 擊中 head-on 幾個月前為我們之前所工作的企業程式庫 5.0 架構的項目。

背景資料

Microsoft 企業程式庫 (Entlib) 是非常知名的發行版本從 Microsoft 典範 & 實例群組。 與日期的 [多個 2 百萬個下載它用於只是大約每隔 niche imaginable 從金融機構和餐廳和醫療設備製造商的政府。 Entlib 是,名可協助解決由許多的企業開發人員共用的一般考量為開發人員的文件庫。 如果您 aren’t 熟悉 Entlib,請看一下我們的網站上之 p & p 開發中心 如需詳細資訊。

Entlib 高度是由組態所主導。 大部分的程式碼是專門用來讀取組態和組合的物件圖形上,根據該組態。 Entlib 物件可以取得非常複雜。 大部分的區塊包含許多選擇性功能。 在另外有 ’s 也很多的基礎結構以支援等也需要取得有線向上的檢測。 因為我們 didn’t 想要以手動方式進行我們的使用者建立檢測提供者,請使用 Entlib 等等只讀取組態,物件的建立封裝工廠物件和靜態的外貌背後。

Entlib 透過第 4 版第 2 版的核心是一個小型的架構,稱為 ObjectBuilder。 ObjectBuilder 是由其作者描述為 「 建置相依性資料隱碼容器的架構 」。企業程式庫是 p & p 專案使用 ObjectBuilder 其中之一 ; 其他包括複合式使用者介面應用程式區塊、 智慧型用戶端軟體工廠和 Web 用戶端軟體工廠。 Entlib 特別花了要心描述 「 架構 」 部分,並建置一大組的 ObjectBuilder 的自訂。 這些自訂項目,提供讀取 Entlib 組態和組合的物件圖形所需的功能。 它們被也需要,在許多情況下股票 ObjectBuilder 實作能改善效能。

缺點是它所花費相當一段時間了解這兩個 ObjectBuilder 本身 (極抽象設計再加上文件的完整缺乏給予 ObjectBuilder deserved 的信譽的複雜性) Entlib 自訂項目。 如此一來大量的學習曲線需要這些只是要開始爬升已通常 stymied 人想要連結的自訂區塊寫入 Entlib ’s 物件建立策略。

然後,新增進一步複雜的因素,Entlib 4.0 我們發行 Unity 相依性資料隱碼容器中。 與 DI 許多的優點,我們想要確定那些會議,供任何原因而無法使用其中許多不錯的開放原始碼容器的客戶對於 DI 從 Microsoft 有很好的選項。 而且我們想要輕鬆取得工作時也使用 Unity Entlib 物件的當然。 在 Entlib 4.0 Unity 整合最後被平行物件建立系統旁邊現有 ObjectBuilder 基礎結構。 現在區塊撰寫者必須知道不只 ObjectBuilder 和 Entlib] 延伸,但同時也 Unity 內部數一些 Entlib 延伸。 不正確的方向步驟。

移動朝簡易性

我們可以在四月 2009年中 Entlib 5.0 上啟動工作。 這個版本的主要佈景主題是 「 簡單的贏得 」。這包含不只是簡單起見,對於一般使用者 (呼叫 Entlib 開發人員),但是 Entlib 本身的程式碼中。 這裡的改良會讓我們維護 Entlib 往,更容易,並將它變成容易瞭解、 自訂和擴充我們的客戶。

我們知道所需的工作的主要區域的其中一個已物件建立管線 — 或我應該說管線嗎? 維護兩個平行但不同集合的程式碼相同的功能是嚴重損壞一個配方。 東西必須採取的動作。

我們設定出針對重構這些目標:

  • 現有的用戶端程式碼 shouldn’t 必須變更只是由於的架構變更。 需要重新編譯是 [確定],但需來源的程式碼變更不課程為用戶端 API 可能變更的 原因)。 內部或擴充性 API 是公平遊戲。
  • 移除多餘的物件建立管線。 我們應該有只有一個方法可以建立不兩個物件 (或更多)。
  • 客戶 don’t 關心 DI shouldn’t 受到 Entlib 在內部使用。
  • 關心 DI 的客戶可以選擇他們想要使用,並取得其物件和 Entlib 物件超出它的容器。

這些目標有不少含意,單獨和組合中。 是 「 一個物件建立管線 」 目標在其表層相當直接了當。 我們決定要完全移除 ObjectBuilder 基礎的系統,然後在內部移與 DI 容器作為我們物件建立引擎。 但我們然後取得 「 shouldn’t 變更現有的用戶端程式碼 」。傳統 Entlib API 是靜態的外貌和工廠的一組。 比方說記錄訊息,使用記錄區塊完成就像這樣:

Logger.Write("My Message");

在罩下 Logger 外貌使用 LogWriter 物件的執行個體來執行實際工時。 記錄器外貌不會因此如何取得 [LogWriter? LogWriter 是具有相依性,一大堆相當複雜的類別,讓您 can’t 只是新它向上,而且預期要適當地有線組態。 我們附到我們記錄器和所有其他的靜態類別在 API 中需要一個全域的容器執行個體結束時。 我們只可以保留通用 Unity 容器,但我們再執行到 「 客戶移至選擇他們想要的容器 」。

我們希望 Unity 和 Entlib 是最高級的經驗的組合。 我們也希望與其他容器也提供該第一個類別經驗。 雖然 DI 容器的一般功能常見跨遊戲區,存取這些功能的方式會廣泛而有所不同。 在實際上許多建立幫手的容器考慮它們的組態 API 是其主要的競爭優勢。 我們如何並對應到全然不同的容器 API 我們 Entlib 組態?

傳統電腦科學方案

它 ’s 電腦科學 truism:在電腦科學中的每個問題可以解決藉由新增的間接層。 然後,’s 完全方式我們解決我們容器獨立性問題。 間接取值層是我們呼叫容器 configurator。 核心 configurator 的角色是讀取 Entlib ’s 組態,並設定以符合的容器。

本身不巧的是,讀取組態 isn’t 足夠。 Entlib ’s 組態檔案格式是著重於最終端使用者。 您設定記錄類別、 例外狀況原則和快取備份儲存區。 它 doesn’t 說的任何項目實際上需要何種物件實作該功能或什麼值傳遞至建構函式或若要設定哪些屬性相關。 DI 容器組態另一方面,而且所有關於 「 對應此介面至這個型別 」 「 呼叫這個建構函式 」 「 將此屬性的設定 」。我們需要另一層的對應至實際的必要物件來實作該區塊的區塊組態的間接取值。 另外一種已有 (需要容器每一個 configurator) 每個 configurator 知道每個區塊的詳細資訊。 立即 untenable ; 程式碼區塊的每一個變更會透過所有 configurators ripple。 以及當有人將寫入自訂區塊時會發生什麼事?

我們最後的物件的集合中,我們呼叫 TypeRegistrations。 各種不同的組態區段負責產生 型別註冊模型 ; TypeRegistration 物件的序列。 介面的 TypeRegistration 的圖 1 所示。

圖 1 的 TypeRegistration 類別

public class TypeRegistration
    {

        public TypeRegistration(LambdaExpression expression);
        public TypeRegistration(LambdaExpression expression, Type serviceType);

        public Type ImplementationType { get; }
        public NewExpression NewExpressionBody { get; }
        public Type ServiceType { get; private set; }
        public string Name { get; set; }

        public static string DefaultName(Type serviceType);
        public static string DefaultName<TServiceType>();

        public LambdaExpression LambdaExpression { get; private set; }

         public bool IsDefault { get; set; }

         public TypeRegistrationLifetime Lifetime { get; set; }

         public IEnumerable<ParameterValue> ConstructorParameters { get; }

         public IEnumerable<InjectedProperty> InjectedProperties { get; }
    }

有 ’s 在這兒,但基本結構是很簡單。 這個類別會描述單一型別必要的組態。 ServiceType 是 [ImplementationType 實際實作之介面的型別時,使用者將從該容器要求的介面。 名稱是服務應該註冊] 下的名稱。 存留期 (Lifetime) 決定單一物件 (傳回相同執行個體每次) 或暫時 (建立新的執行個體每次) 建立的行為。 依此類推。 我們選擇使用 Lambda 運算式來建立 TypeRegistration 物件,因為它使得很容易在單一,光碟中指定此資訊特別色。 這裡 ’s 從資料存取區塊建立型別註冊的範例:

yield return new TypeRegistration<Database>(
       () => new SqlDatabase(
           ConnectionString,
           Container.Resolved<IDataInstrumentationProvider>(Name)))
       {
           Name = Name,
           Lifetime = TypeRegistrationLifetime.Transient
       };

這個型別註冊說出 「 時要求資料庫,名為名稱,傳回一個新 SqlDatabase 物件,使用 ConnectionString 和一個 IDataInstrumentationProvider 建構 」。關於此處使用 Lambda 不錯的事情是,在撰寫該區塊時我們可以建立這些運算式就如同我們直接找 newing 向上物件。 編譯器將輸入核取運算式,那麼我們不小心 won’t 嘗試呼叫 doesn’t 存在的建構函式。 若要設定屬性,您只是使用 C# 物件初始設定式語法 Lambda 運算式內。 TypeRegistration 類別會負責透過 Lambda 挑選與等等萃取建構函式簽章,[參數],[型別,讓 configurator 作者 doesn’t 需要的細節。

一個有用的技巧,我們使用為該呼叫 「 Container.Resolved 」。 該方法 doesn’t 實際執行任何動作 ; 在實際上它的實作是只是這樣:

public static T Resolved<T>(string name)
        {
            return default(T);
        }

為什麼會有? 請記住這個 Lambda 運算式永遠不會實際執行。 而是,我們逐步運算式在執行階段,若要拉出註冊資訊的結構。 這個方法是只是已知的標記。 當我們找到 Container.Resolved 做為參數的呼叫時,我們解譯,如 「 解決這個參數來透過容器 」。我們找到此標記方法技巧會很有用的地方以各種進行與運算式樹狀結構的進階的工作時。

在結束流程的組態檔來設定容器看起來像是 的 圖 2


圖 2 的 容器組態

我們所做的一個設計決策是這裡解釋很重要的。 系統不,而且絕不可能,具有一般用途 TypeRegistration 設定-一切任何 DI 容器的抽象概念。 它是特別針對企業程式庫專案的需求而設計。 典範與實例小組不定位這為程式碼指引。 雖然基本的概念 (解壓縮您的設定成抽象模型) 是通常適用特定的實作是針對 Entlib 只。

取得容器中的物件

讓我們放大設定我們容器。 ’s 半場戰役。 但是如何做得到物件回出嗎? 容器介面有所不同這同時也雖然謝天謝地不儘其組態介面執行。

幸好,我們 didn’t 有創造一個新的抽象概念。 inspired 由在 2008年夏天的 部落 Jeremy Miller 從 、 模式 (& I) 實務群組、 MEF 小組在 Microsoft 和許多不同 DI 容器一起運作以定義來解析容器中的物件的最低共同要素的作者。 這已發佈至 Codeplex 和 MSDN 為 通用服務定址器專案 。 這個介面提供我們完全什麼我們需要 ; 從企業程式庫內每當我們需要用來取得該容器用完物件我們可以透過這個介面來呼叫並絕緣從所使用的特定容器。 下一個問題的當然是:其中 ’s 容器?

企業程式庫 doesn’t 有任何一種啟動安裝需求。 使用 [靜態外貌時 don’t 需要任何一處呼叫初始化函式。 原始程式庫提取組態時第一次所需的工作。 我們必須複寫這個行為,使文件庫會是就緒時呼叫。

我們的需要是一個標準熟知用於取得正確設定的容器。 通用服務定址器媒體櫃實際上有一種:ServiceLocator.Current 靜態屬性。 我們決定不想使用這幾個原因。 主要的原因是可以用 [ServiceLocator.Current,由其他程式庫或甚至應用程式本身。 我們需要能夠在第一次存取任何 Entlib 物件的設定容器 ; 任何其他已頭髮遺失一個配方隨著人嘗試要算出,為什麼他們仔細建構的容器消失,或 Entlib 第一個呼叫的處理,但事後 didn’t 的原因。 第二個原因有與介面本身中的缺點。 有 ’s 沒有查詢,找出是否它被設定屬性的方法。 進行硬決定何時設定容器。

所以,我們建立我們自己的靜態屬性:EnterpriseLibraryContainer.Current。 您可以從使用者程式碼同時也將此屬性,但是它 ’s 特別的企業程式庫的一部分,因此有 ’s 較少機會與其他程式庫或主要應用程式相衝突。 在第一次呼叫靜態外貌,檢查 EnterpriseLibraryContainer.Current。 如果它設定您可以使用任何項目已存在。 如果,沒有您建立 UnityContainer 物件、 使用一個 configurator 加以設定,並將它設為目前屬性的值。

這結果是現在有三種不同方法來存取企業程式庫功能。 如果您使用 [傳統 API 的所有項目只是運作。 在罩下的 Unity 容器會建立,和使用。 如果應用程式中使用不同的 DI 容器和 don’t 要 Unity 出現在您的處理程序,但仍在使用傳統的 API,您可以設定您的容器使用一個 configurator、 包裝在一個 IServiceLocator,停在 EnterpriseLibraryContainer.Current,和再外貌會繼續運作。 只有現在對方使用您在罩下所選擇的容器。 我們實際上 don’t Unity 提供在主要 Entlib 專案以外的任何容器 configurators ; 我們希望社群會實作它們的其他容器。

第二個選項是直接使用 EnterpriseLibraryContainer.Current。 您可以呼叫 GetInstance < T > (),以取得任何企業程式庫物件並它賦予您其中一個。 而且如果希望您可以再次,停其背後的不同容器。

最後,您可以只是您所選擇的容器直接使用。 您必須啟動 Entlib 組態加入至容器使用一個 configurator 但讓這 isn’t 新的需求如果使用容器,需要無論如何,設定。 從該處,您只需要插入 [Entlib 您想為相依性,而且 ’re 關閉的物件及執行。

我們如何?

let’s 回查看我們組的目標,並查看這項設計最多的堆疊。

  1. 現有的用戶端程式碼 shouldn’t 必須變更只是由於的架構變更。 需要重新編譯是確定,但需來源的程式碼變更不課程為用戶端 API 可能變更的 原因)。 內部或擴充性 API 是公平遊戲。

    符合。 原始的 API 仍然可以運作不變。 如果您 don’t 關心相依性的插入,您兩者都不需要知道也不在意您的物件如何向上有線相扣的。

  2. 移除多餘的物件建立管線。 我們應該有只有一個方法可以建立不兩個物件 (或更多)。

    符合。 從程式碼基底消失 ObjectBuilder 堆疊 ; 所有項目現在建置透過 TypeRegistration 和 configurator 機制。 您確實需要一個 configurator 每容器。

  3. 客戶 don’t 關心 DI shouldn’t 受到 Entlib 在內部使用。

    符合。 DI 並不會呈現本身除非您希望它。

  4. 關心 DI 的客戶可以選擇他們想要使用,並取得其物件和 Entlib 物件超出它的容器。

    符合。 您可的使用直接的選擇,或您 DI 容器可以讓它在靜態外貌背後涵蓋下使用。

我們最後有一些額外的好處。 Entlib 程式碼基底有更簡單。 我們最後約 200 類別刪除原始的實作。 加入型別註冊片段之後, 我們往下大約 80 類別總之後,因素重建已完成。 此外,已加入的類別比已移除的簡單而且整體結構已大幅更一致,與較少的移動部分或特殊情況。

另一個優點是重整的版本開啟要比在原始與顯示百分之 10 效能增益的某些初始、 非正式度量有點快。 對我們所見的數字之後這實際上進行有意義我們。 大部分的原始的程式碼中複雜性是來自一系列的解決 ObjectBuilder ’s 慢速實作的效能最佳化。 多數 DI 容器也都完成其一般效能上的重大工作。 藉由重建 Entlib 容器的頂端,我們可以利用該效能工作,並且不需要執行此項作業很多自己。 Unity 和其他容器發展,並進一步最佳化,無須我們組件上的精力整個大量 Entlib 應該更快取得。

其他程式庫已學習的課程

企業程式庫是真的會聯繫到一個沒有被硬式相依性注入容器善用的程式庫的極佳範例。 如果您喜歡撰寫一個使用 DI 容器,但 doesn’t 上您的消費者強制您所選擇的程式庫,我們希望您可以找到一些設計 inspiration 從我們的範例。 我認為我們的目標,尤其是這些最後兩變更是相關 任何 程式庫作者,不只是 Entlib:

  • 客戶 don’t 關心 DI shouldn’t 受到 Entlib 在內部使用。
  • 關心 DI 的客戶可以選擇他們想要使用,並取得其物件和 Entlib 物件超出它的容器。

在設計您的媒體櫃時沒有您需要考慮的幾個問題。 請務必考慮這些:

  • 如何 bootstrapped 媒體櫃? 執行您的用戶端進行取得設定,程式碼的特定或者嗎您應只可使用的靜態的進入點吗?
  • 如何您建立模型的物件圖形,讓容器可以設定而您不必硬式程式碼呼叫該容器? 請看一下 inspiration 我們 TypeRegistration 系統。
  • 如何將的容器使用管理? 它要在內部,處理或執行您的呼叫端管理它嗎? 如何呼叫者告訴? 您要使用哪一個容器

我們想出這些問題的答案的良好的集合為我們的專案。 我希望能在我們的範例可以提供 inspiration 設計您自己時。

Chris Tavares 上 Microsoft 模式是開發人員 & 實行方式他所在企業程式庫和 Unity 開發負責人的小組。 之前到 Microsoft,他參加諮詢壓縮換行功能的軟體,並內嵌系統。 他 Entlib、 p & p 和 tavaresstudios.com 在一般程式開發主題相關的部落格。