先說下為什麼翻譯這篇文章,既定的方向是架構,然後為了學習架構就去學習一些架構模式、設計思想。 突然有一天發現依賴註入這種技能。為了使得架構可測試、易維護、可擴展,需要架構設計為松耦合類型,簡單的說也就是解耦。為瞭解耦前面的人提出各種理論,主要思想是控制反轉,而現在主流的主要是兩個:依賴註入、服務定位 ...
先說下為什麼翻譯這篇文章,既定的方向是架構,然後為了學習架構就去學習一些架構模式、設計思想。
突然有一天發現依賴註入這種技能。為了使得架構可測試、易維護、可擴展,需要架構設計為松耦合類型,簡單的說也就是解耦。為瞭解耦前面的人提出各種理論,主要思想是控制反轉,而現在主流的主要是兩個:依賴註入、服務定位(有篇英文文章特意討論這種模式,最終的結論是否定的,乍看了一眼,沒看懂)
有了某個思想便可以在程式中體現,用多了,人們就去對它進行封裝,普通的人封裝的大概就自己用,高人封裝了便成了組件。現在基本上都在弄 .net ,所以說的技術也基本上是這一塊的。.net 的 IOC 框架有 Autofac、Castle Windsor、Unity、Spring.NET、StructureMap、Ninject。
有這麼多框架為什麼選 Autofac,我在這個地方只是學習這種 IOC 思想的框架,並沒有真實的用在生產中。Autofac 各方面的的性能都比較中庸,所以用它作為 IOC 入門應該是比較合適的,這樣也就導致這篇文章適合初學者閱讀。
接下來就要具體說下翻譯的原文,文章對於 Autofac 在 IOC 思想的實現和 Autofac 如何使用做了十分透徹的說明,但是本人水平有限,一些地方沒有翻譯對,甚至翻譯錯了,這些我特意指明,省的坑了各位,如果發現不對直接參考原文,但也請告知我,以便改正。
原文地址:http://www.codeproject.com/Articles/25380/Dependency-Injection-with-Autofac
----------------------------------------------------------------------------------------------------------------------------------------------
Dependency Injection with Autofac
使用 Autofac 進行依賴註入
目錄
Introduction-介紹
Autofac 是發佈在 Google Code 上的依賴註入或控制反轉的開源容器。
Autofac 與許多類似技術不同的是它堅持儘可能的使用純質的 C# 語法。【也就說不去依賴其它的組件,從而使得 Autofac 有著更好的相容性】
等等
The Example Application-應用程式示例
這個程式是一個控制台程式,它檢查一個備忘錄列表,每一個備忘錄有一個過期日期,程式向用戶提醒哪些過期的備忘錄。
Checking for Overdue Memos-檢查過期備忘錄
以下幾點作為使用依賴註入的直接原因:
1 以參數接收所有依賴的對象
2 獨立的持久化-IQueryable 對象可能是資料庫表、一個結構文件,再或者記憶體集合。(集合公佈)
3 提醒用戶的形式是獨立的。(以介面控制)
這些舉措使得這個類容易測試,可配置的並且是可維護的。
Notifying the User-提醒用戶
Data Storage-數據倉儲
IQueryable 介面在 dotnet 3.5 中引入,它適合作為備忘錄的數據源。因為它可以同時用於記憶體對象和關係型資料庫的查詢。
Wiring Up Components-連接組件
應用程式最基礎的結構如下:
本文主要關註的是 MemoCHecker 如何獲取與它關聯的 notifier 和 memo 服務,以及每個被依賴的對象如何獲取他們的實體。【有個反轉的概念】
Dependency Injection by Hand-手動依賴
更重要的是,它很難去切換不同的實現服務;比如可能用 EmailNotifier 替換 printing notifier,但是原有的會有依賴,或許不同來源於這些 PrintingNotifier【不理解】,但也有可能是與依賴的其它組件有交互。(這也可能是這些組合現成的問題,而它本身就值得寫文說明)
Autofac 和其它的依賴組件通過在配置時“扁平化”深度網狀對象圖。【待譯】
Dependency Injection with a Container-容器依賴
當使用 Autofac,訪問 MemoChecker 是分離的來源於以下創建方式:
Container.Resolve() 執行請求一個 MemoChecker 的實例,它負責實例的實例化和準備使用。那麼,容器是如何工作的、如何創建 MemoChecker 實例。
Component Registrations-註冊組件
依賴註入容器是一個把服務映射到組件的集合。在這種情形下,服務是用來識別某一特定的功能,它可以是一個文本標簽,但其通常是某個介面。
註冊器在系統內部捕獲組件動態的行為。其最受關註點是如何創建組件的實例。
Autofac 能夠接受創建組件的註冊方式有:表達式、提供實例和基於 System.Type 反射。【待譯】
Registering a Component Create with Expression-使用表達式註冊組件
下麵就是為 MemoChecker 組件設置一個註冊器:
每一條 Register() 語句僅只處理那些處於對象圖頂端的且其直接關聯到依賴對象上。
C=> new MemoChecker(…) 將被用於容器創建 MemoChecker 組件。
每個 MemoChecker 依賴於額外的兩個服務:IQueryable<Memo> 和 IMemoDueNotifier 。這些服務通過容器調用 Resolve() 方法在 lambda 表達式內檢索到,容器是通過參數 c 傳遞過來的。
上面的註冊器並沒有對 IQueryabl<Meno> 和 IMemoDueNotifier 的實現進行任何說明。這兩個服務的配置依賴關係同 MemoChecker 的配置類似。
上面的表達式將被提供於 Register() 當其返回 MemoChecker 類型,於是,Autofac 會將其作為這個註冊器的預設服務,除非有另外的更為準確的 As() 方法,下麵這個 As() 方法包含更為明確的目的:
無論哪種方式,某個 MemoChecker 實例的請求都將是調用我們的表達式後的某一結果。
Autofac 不會在組件註冊時執行表達式,相反,它會等到 Resolve<MemoChecker>() 調用時執行表達式。這一點很關鍵,因為它除去了組件註冊的順序的依賴。【依賴關係不受註冊順序影響】
Registering a Component Instance-使用實例註冊組件
IQueryable<Memo> 服務由存在的 memos 實例提供,PrintingMemoNotifier 類最終指向 TextWriter 的實例 Console.Out 。
Memos 和 Console.Out 都以已經創建的實例提供給容器。(對於 ExternallyOwned() 的解釋,請參考 Deterministic Disposal)
Registering a Component with its Implementation Type-使用實現類型註冊組件
Autofac 也可以用其它容器創建組件的方式-反射來創建組件。(更多這個場景的優化伴隨著 MSIL-generation)
這種方式的含義是說你可以告訴 Autofac 有關於提供服務的類型,並且容器將會使用最合適的構造函數,以及根據其它可用的服務來選擇參數。
MemoChecker 註冊可以被下麵的形式替換:
一般說來,最常見的做法是使用自動連接去註冊某一批次的組件。【待譯】
這種方式使得大量的組件可以使用但沒有繁重的註冊每一個組件的消耗,並且你需要清楚的思考這種情形。Autofac 提供如下快捷的方式批次註冊組件:
自動連接在以程式 XML 配置文件註冊組件時也特別實用。
Completing the Example-完整示例
在請求 MemoChecker 服務之前程式創建註冊組件如下所示:
在上面的配置代碼中沒有嵌套顯示了“扁平”的依賴結構而這些由容器提供。
這似乎很難看到上面這種註冊方式比手動的例子更為之間的對象結構,但請再次回憶起,這個示例比平常的系統擁有更少的組件。
最為重要的區別是現在每個組件的配置都獨立於所有其他的組件。伴隨著更多的組件添加到系統中,他們可以被理解為純凈的按照這些服務所暴露的河這些服務所需要的。這是一種有效的控制架構複雜化的手段。
Deterministic Disposal-指定清理
IDisposable 兼具祝福和詛咒。組件有一致的方式去同其應當被清理交互這是好的。不幸的是,哪個組件在什麼時候應當被清理並不總是很容易確定。
這個問題由於允許相同的服務有不同的實現而變得糟糕。在這個示例中,IMemoDueNotifier 有許多不同的實現可能被部署。其中的一些將有工廠創建,一些將會是單例的形式,一些將會被清理,還有一些將不會被清理。
組件作為一個通告者是沒有辦法決定它們應當嘗試把它丟給 IDisposable 和調用 Dispose() 或者是不這樣做。各種記錄的結果是導致易錯的河繁瑣的。
Autofac 使用通過容器跟蹤創建的所有可以清理對象方式解決這個問題。記錄的示例如下:
容器在一個 using 程式塊中,因為它擁有所有它創建的組件的所有權,並且清理它們當容器被清理的時候。
這一點很重要因為它真正實現了從配置關註分離的精神【待譯】,MemoChecker 服務可以在任何需要它的時候使用,甚至是以另外被依賴的組件角色被直接創建,不用擔心它們是否應當被清理。
伴隨著這個帶來的內心的平靜,你甚至不用來回讀示常式序來發現任何需要實現 IDisposable (實際是沒有)的類,因為你可以依賴容器去做正確的事情。
Disabling Disposal
記住 ExternallyOwned() 子句在上面完整的示例中添加到 Console.Out 的註冊上。這是合意的因為 Console.Out 是可清理的,但這個時候容器不應當去清理它。
Fine-Grained Control of Component Lifetimes-精細控制組件生命周期
容器將會正常的存在於應用程式執行期間,並且在相同應用程式長生命周期內清理它是一個很好的方式去釋放組件所擁有的資源。大多數不平凡的程式也應釋放資源在一些其它的時候,如:Http 請求完成、工作線程退出或者一個用戶會話結束。
Autofac 使用嵌套的生命周期域幫助你管理這些生命周期,代碼如下:
生命周期管理是通過註冊組件實例映射到生命周期域實現的。
Component Lifetime-組件生命周期
Autofac 允許你指定一個組件多少實例可以駐留以及它們將如何在其它組件間共用。
控制組件獨立的定義作用域是一個非常重要的改進對於傳統方法使用靜態 Instance 屬性來定義單例。這個區別在於對象是什麼和如何使用對象。【待譯】
使用 Autofac 最常用生命周期設置如下:
單例
每一依賴一個實例
每一生命作用域一個實例
Single Instance-單例
單例生命周期,它將是大多數組件只有一個實例在容器中的選擇,並且實例會隨著創建它的容器清理時被清理。
一個組件可以使用 SingleInstance() 修飾配置為這種生命周期,如下所示:
每次這樣的組件從容器請求時,都會返回相同的實例:
Instance Per Dependency-每一依賴一個實例
當組件註冊時沒有特意指定生命周期,將會預設 instance-per-dependency 生命周期。每次從容器獲取這樣的組件時,都會返回新建的實例:
某個使用這種生命周期的組件將會跟隨著組件被創建的生命周期而被清理。如果一個 per-dependency 組件被請求是去構造一個 single-instance 組件,對於這種例子,那麼 per-denpendency 組件將會隨著 single-instance 組件對於容器的生命周期一直存在。
Instance per Lifetime Scope-每一生命周期作用域一個實例
這種方式滿足每一線程、每一請求或者每一事務組件的生命周期的靈活需求。簡單創建一個生命作用域可以生存在必須的生命周期持續的周期。【待譯】來自相同的作用域請求將會得到相同的實例,同時來自不同作用域的請求將會得到不同的實例。
Using Scope to Control Visibility-使用作用域去控制組件依賴的可見性
組件間的依賴僅只在其滿足其它的組件在其相同的作用域或者在其外部(父)作用域才能建立。這樣確保組件間的依賴在其沒有建立前被處理掉。【待譯】如果 application、session 和Request 有著嵌套的需求,那麼可能會按照如下的方式創建:
在這個示例中請知道,appContainer 將會創建許多子 sessionLifetime,並且每個 session 同appContainer 一樣會有許多子 controller。【待譯】
在這個場景,允許依賴的方向是 Request -> session -> application。用於處理用戶請求的組件可以引用任何其他的組件。但是依賴關係在相反的方向上是不被允許的,所以,對於這個情況,應用程式級別的 single-instance 組件將不會連接到指定單例用戶 session 的組件。【待譯】
在這樣的層次結構中,Autofac 總是服務於 shortest-lived 生命周期的組件請求。這將會平常的請求生命周期。【待譯】Single-instance 組件將會駐留在應用程式級別,來將組件的生命周期關聯到 session 級別,詳情請參考 wiki。【待譯】
Autofac 的作用域模式是靈活和有效的。作用域和嵌套生命周期間的關係使得註冊龐大的依賴成員成為可能。
Autofac in Application-在程式中使用 Autofac
依賴註入是一種極其強大的結構控制機制,但是想要獲取這些優勢,系統的組件通過容器成為其它組件能用的占比十分重要。
到目前為止 Autofac 所描述的特征都是被設計為獲取存在的,當“老的簡樸的” .Net 組件添加到容器中,不需要修改或適配代碼。
Expressive Registrations-顯示註冊
使用表達式來向容器註冊組件在使用 Autofac 的應用程式中。一些示例場景說明這些由 Autofac 實現的:
已經存在的工廠方法可以用表達式的形式使用:
已經存在需要在第一次訪問時載入的單例可以使用表達式註冊,並且它的載入是延遲的:
傳遞給組件的參數可以使任何的來源:
某個類型的實現甚至可以根據參數來決定:
Simplified Integration-簡單集成
集成,在這裡的意思是使得已存在的類庫服務和程式組件通過容器使他們可以使用。
Autofac 支持一些典型場景的集成比如在 ASP.Net 應用程式中使用的那樣;然而,Autofac 模型的靈活性產生了大量的繁瑣的集成工作,因而最好的方式是把這些留給程式設計者在他們程式最合適的地方實現。
基於表達式的註冊和指定的清理以及延遲載入組件的方案,可以產生令人驚奇的效果當它們集成在一起時:
這是一個 WCF client 集成自 Autofac 網站的示例。ITrackListing 和 IChannelFactory<ITrackkListing> 這兩個關鍵的服務,WCF 管道對於這些通用的 bit 流很容易基於表達式進行註冊。【待譯】
一些要點如下:
Channel 工廠只有在其需要的時候創建,但是一旦創建,它將保留以在每一次 ITrackListing 請求時重用。
ITrackListing 不繼承自 IDisposable,但在 WCF 中,客戶端服務代理以這種方式創建需要聯繫到 IDisposable 並且清理它。使用 ITRackListing 介面,使得仍然不知道其實現細節。
終端信息可以來自任何地方-任意服務、資料庫或者某個配置文件。
除了使用基本的 Register() 方法沒有其他概念引入。
這小節向你展示 Autofac 是如何工作的,使得你集中於實現你的應用程式,而不是擴展或驚奇 DI 容器的複雜性。
Where to Next?
我希望這篇文章用來說明來學習如何使用 Autofac 的各種要點,接下來的步驟可能是:
下載源代碼
在 Wiki 上閱讀更多文章
在論壇里介紹你自己