1、IoC/DI簡介 IoC 即 Inversion of Control,DI 即 Dependency Injection,前一個中文含義為控制反轉,後一個譯為依賴註入,可以理解成一種編程模式,詳細的說明可參見大牛Martin Fowler的強文 http://martinfowler.com/ ...
1、IoC/DI簡介
IoC 即 Inversion of Control,DI 即 Dependency Injection,前一個中文含義為控制反轉,後一個譯為依賴註入,可以理解成一種編程模式,詳細的說明可參見大牛Martin Fowler的強文 http://martinfowler.com/articles/injection.html,借用Hollywood的名言:Don't call us, we'll call you,意即你呆著別動,到時我會找你。控制反轉的核心是控制權的轉移,從原有的應用程式轉移到框架如IoC容器,從而實現模塊間的解耦。
2、Unity是什麼?
Unity是微軟Patterns & Practices團隊所開發的一個輕量級的,並且可擴展的依賴註入(Dependency Injection)容器,它支持常用的三種依賴註入方式:構造器註入(Constructor Injection)、屬性註入(Property Injection),以及方法調用註入(Method Call Injection)。現在Unity最新的版本的3.5版,可以在微軟的開源站點:https://github.com/unitycontainer/unity下載最新的發佈版本和文檔。
它有助於構建松耦合的應用程式和為開發者提供以下便利:
- 簡化對象的創建,特別在分層對象結構和依賴的情形下;
- 它支持需求的抽象化,這允許開發人員在運行時或在配置文件中指定依賴,簡化橫切關註點(crosscutting concerns)的管理;
- 它通過把組件配置推給容器來決定,增加了靈活性;
- 服務定位能力; 這使客戶端能夠存儲或緩存容器;
- 輕鬆構建松耦合結構的程式,從而讓整個程式框架變得清晰和易於維護。
3、如何使用Unity?
下麵我們用一個簡單的例子來演示如何使用Ioc框架:Unity。我們的大多數應用程式都是由兩個或是更多的類通過彼此的合作來實現業務邏輯,這使得每個對象都需要獲取與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那麼這將導致代碼高度耦合併且難以維護和調試。使用Unity就是要解決這個這個問題。
Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴註入劃分為三種形式,即構造器註入、屬性(設置)註入和介面註入,習慣將其劃分為一種(類型)匹配和三種註入:
- 類型匹配(Type Matching):雖然我們通過介面(或者抽象類)來進行服務調用,但是服務本身還是實現在某個具體的服務類型中,這就需要某個類型註冊機制來解決服務介面和服務實現類型之間的匹配關係;
- 構造器註入(Constructor Injection):IoC容器會智能選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前解析註冊的依賴關係並自行獲得相應參數對象;
- 屬性註入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性;
- 方法註入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。
依賴翻轉的核心原則:
1、高層模塊不應該依賴底層模塊,兩個都應該依賴抽象(抽象類或介面);
2、抽象不應該依賴細節,細節應該依賴抽象。
下麵我採用“屬性註入(Property Injection)”的方式演示如何使用Unity框架。
3.1、定義介面類
一個簡單日誌記錄介面類,用於記錄請求的報文。
public interface ILogger { Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null); void Log(Tuple<string, string> logInfo); }
3.2、定義介面的實現類
定義2個介面的實現類,分別用來實現對請求的響應的報文進行日誌記錄。
internal class LogRequest : ILogger { public Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null) { var reqType = request.GetType().Name; switch (reqType) { case "AtomQueryRequest": { var req = (AtomQueryRequest) request; return Tuple.Create(request.CollectionId, string.Format("流水號:{0} 交易類型:{1}", req.TransNo, req.TransType)); } case "AtomSaleRequest": { var req = (AtomSaleRequest) request; return Tuple.Create(req.CollectionId, string.Format("流水號:{0} 訂單號:{1} 金額:{2}", req.TransNo, req.OrderNo, req.LocalAmount)); } default: { throw new CheckRequestException("無效的交易類型:".Contact(reqType)); } } } public void Log(Tuple<string, string> logInfo) { LogManager.InfoRequest(logInfo.Item1, logInfo.Item2); } }
3.3、定義容器並註冊介面及介面實現類之間的映射關係
項目引用:Microsoft.Practices.Unity.dll
internal class ServiceContainer { // 核心容器類型的定義,設置為靜態的,公開的,可為其它任何類型調用。 public static UnityContainer RtpContainer; // 初始化容器併在容器中註冊項目程式中所有的依賴關係 static ServiceContainer() { RtpContainer = new UnityContainer(); // 每次調用,容器都會生成一個新的對象實例 RtpContainer.RegisterType<IResponseProcessor, ResponseProcessor>(); // 註冊為單例,任何時候調用都使用同一個對象實例 RtpContainer.RegisterType<ILogger, LogRequest>("Request", new ContainerControlledLifetimeManager()); RtpContainer.RegisterType<ILogger, LogResponse>("Response", new ContainerControlledLifetimeManager()); } }
什麼時候註冊為單例,我的個人標準為:實現類,比如:LogRequest類,沒有靜態成員或者靜態成員沒有併發寫的可能都可以用,使用單例可以減少頻繁創建對象可能造成的開銷。在註冊類型時,如果要註冊為單例模式,額外傳入一個:new ContainerControlledLifetimeManager() 參數即可,表示創建對象的生命周期由容器來控制。另外如果一個介面由多個實現類,如上面的LogRequest和LogResponse都實現了ILogger介面。這樣在註冊map映射關係時,需要額外使用一個name參數(比如上面的“Request”,“Response”)來唯一標識map關係。使用XML也可以實現依賴關係的註冊,但我更傾向於使用:約定優於配置(convention over configuration)的原則,也稱作按約定編程,是一種軟體設計範式,旨在減少軟體開發人員需做決定的數量,獲得簡單的好處,而又不失靈活性。
3.4、依賴註入
internal abstract class AbstractRequest { static AbstractRequest() { JsConfig.EmitCamelCaseNames = true; JsConfig.IncludeNullValues = true; } [Dependency("Request")] public ILogger Logger { get; set; } [Dependency] public IRequestCheck RequestCheck { get; set; } protected virtual void CheckRequest(AtomRequest request) { if (null == request) { throw new ArgumentNullException("request", "請求實體不能為NULL"); } } }
上面的代碼我採用了“屬性註入(Property Injection)”的方式註入了一個Logger屬性,並且Dependency屬性類的name參數為:Request,標識當AbstractRequest的實現類被實例化時,IoC容器自動初始化該Logger屬性為一個LogRequest對象實例。
3.5、使用註入的屬性
internal class ProcessAtomSaleRequest : AbstractRequest, IAtomRequest { [Dependency("AtomSale")] public MessageProviderFactory MessageProviderFactory { get; set; } public object Execute(AtomRequest request) { CheckRequest(request); // 使用AbstractRequest類中註入的屬性Logger,ProcessAtomSaleRequest被實例化時Logger屬性自動被初始化為LogRequest實例對象 Logger.Log(Logger.GetLogContent(request)); } }
3.6、使用容器來解析並創建對象實例
public class AtomSale : AtomTransaction { protected override object ProcessRequest(AtomRequest request) { return ((IAtomRequest)ServiceContainer.RtpContainer.Resolve(typeof(IAtomRequest), "AtomSale")).Execute(request); } }
ServiceContainer.RtpContainer就是我們前面定義的靜態的、公共的容器類(類型為:UnityContainer),在第一次被調用時初始化。Resolve方法通過指定抽象類型及對應的name屬性來確定唯一映射關係並創建對象,最後執行對象的Execute方法。
總結:
使用IoC框架後,使用相同架構模型的應用,其高層抽象可以完全移植,只須關註實現類的業務細節即可,業務邏輯修改時也只須改動實現的部分,這樣就實現了抽象層和實現層的分離,同樣對象創建職責也做了轉移。系統解耦或松耦合也就順理成章了。如果所有代碼都揉合在一起,任何代碼的修改都可能對其它代碼造成影響,如果沒能細緻和全面的回歸測試,線上故障也難免發生。