初衷
發現學習東西不單只是看,用,還有很重要一點就是記錄,不然過個幾個月再用到相同的知識時,你會發現你已經丟得差不多了,故此開始在博客園記錄的同時也與各位同行分享知識。
正題
關於IOC,在剛工作時就接觸到了這個技術,原諒我當時年輕不求甚解,以為IOC就僅僅只是控制反轉,只要不直接創建我所需要的類就OK。
當時公司的框架正在使用Spring.Net,使用了Spring的IOC解耦的標准三層架構。
效仿其結構,當時我自己用反射做了一個簡單的IOC容器,當時甚至還不知道什么叫IOC容器,只是感覺自己做了一個有點特別的工廠。
還自以為已經掌握了IOC而沾沾自喜,后來慢慢接觸很多優秀的開源框架(ABP,Enode等),看了一些DDD(領域驅動設計)的書,才發現當初自己了解的IOC不過冰山一角。
所以一直很想寫寫關於這方面的一些總結與經驗。
本篇作為開篇,還是想先對不了解IOC的朋友做個簡單的介紹。
話不多說,我們來看看例子。
首先看看不使用IOC情況下的例子:
public void GetServiceData() { var service = new DataService(); var dataList = service.GetDataSource(); Console.WriteLine("Get Data:" + dataList.ToString()); }
這個簡單的例子里,我們所獲取的數據由DataService類提供,我們直接創建了一個新的實例來使用它,這樣一來就與該類產生了強依賴,如果DataService發生了變化,比如名字變更,或者業務需要我們使用另一種數據服務NewDataService時,
我們都不得不去更改代碼,這違反了OCP(開閉原則)。
如果讀過設計模式的看官可能會想到工廠模式,好的,我們來嘗試一下。
public class ServiceFactory { public static DataService GetDataService() { var component = new DataService(); return component; } }
我們把實體的創建放到了一個靜態工廠里面,然后源代碼變成這樣了。
public void GetServiceData() { var dataList = ServiceFactory.GetDataService().GetDataSource(); Console.WriteLine("Get Data:" + dataList.ToString()); }
看上去似乎不錯,對Service層的依賴已經不存在了。
但又增加了新的依賴——ServiceFactory,代碼仍然不夠優雅。
不過值得一提的是,工廠通常來說變化是很小的,這種形式的解耦還是可以接受的。
剛才提到的問題還有一個很重要的點沒有解決——當業務需要我們用一個具有相同行為的服務去替換,或者拓展時。
業務場景來說的話,就比如到醫院就診這個服務,大致的行為流程可以分為:掛號,等待,就診,結賬這四個行為。
我們在完成人民醫院的就診服務調用后,可能以后會需要調用門診部或者其他醫院的就診服務,而這幾個服務的行為是一致或相近的,無非實現方法不一樣,比如掛號,可能是在前台掛也可能是通過機器掛,但都是掛號。
這個時候我們如果去修改調用就診服務的代碼塊就不是一個理想的做法,所以我們要遵從DIP(依賴倒置原則)來進行編程,簡單來說,就是針對抽象而不是具體類,抽象包括抽象類和接口。
我們用接口來改造上面的代碼。
public interface IDataService { int GetDataSource(); } public class DataService : IDataService { public int GetDataSource() { return 0; } } public class NewDataService : IDataService { public int GetDataSource() { return 1; } }
調用代碼:
public class ServiceFactory { public static IDataService GetDataService() { var component = new DataService(); return component; } } public void GetServiceData() { var dataList = ServiceFactory.GetDataService().GetDataSource(); Console.WriteLine("Get Data:" + dataList.ToString()); }
好的,現在我們需要更換成另外一個數據服務時,只要修改工廠里的方法就行了……
好吧,這樣看來我們只是把問題的發生場所換到工廠里了而已,
不過不要沮喪,我們已經進了一步。想象一下,如果你的項目里有數十個用到DataService的地方,我想你在修改的時候會有遺漏的風險。
然而如果放在工廠里去修改,我們只需要修改工廠的代碼就行了,雖然依舊違反了OCP,但它確實起到了集中管理的良好效果。
是時候探討一下工廠模式以外的解耦方法了,考察調用的方法里,我們只得到了一個接IDataService。
既然如此,那一開始就把這個接口放在類里面不就好了?
聽上去似乎不錯,不過它的具體實現又該從哪里得到呢?
思考一下一個類最常與外部做數據交互的東西有兩個,
一,屬性
二,方法參數
好的,得到這兩點信息后,我們考慮如何實現。
為了保證類在調用服務時,我們的具體實現已經被傳入了類當中,我們必須在類實例化的時候就給他傳入具體實現,這個過程就是我們耳熟能詳的DI(依賴注入)
如果我們通過屬性去注入,那就是屬性注入。
而選擇方法,就被稱為方法注入,由於我們必須在實例化時注入依賴,最佳選擇肯定是在構造器中傳入依賴,這種情況就被稱為構造器注入。
讓我們來看一下兩種注入方法的實現。
屬性注入:
public IDataService IDataService { get; set; } public void GetServiceData() { var dataList = IDataService.GetDataSource(); Console.WriteLine("Get Data:" + dataList.ToString()); }
構造器注入
public class CallClass { private IDataService _dataService; public CallClass(IDataService injectService) { _dataService = injectService; } public void GetServiceData() { var dataList = _dataService.GetDataSource(); Console.WriteLine("Get Data:" + dataList.ToString()); } }
這樣我們的調用類就完全從依賴中解放出來,只要暴露出它所需要的行為,具體實現由外部創建它的類來實現。
通用規則是使用構造器注入來注入一些我們必須的依賴,比如應用服務層的業務服務
使用屬性注入來注入一些可選的參數,比如日志服務。
在Autofac的官方手冊的最佳實踐中提到了盡量使用構造器注入。
由此可見構造器注入的常用性。
到此為止對IOC和DI的介紹就此結束。
可能有的看官會問,那外部創建它的類不是也要考慮具體注入哪個類不是嗎?
這就涉及具體項目的使用問題了,我們使用IOC框架就在於更靈活而可控地去注入我們所需要的依賴。
所以下來的隨筆我逐一介紹當前最流行的幾個IOC框架,並比較它們的易用性和一些不同點。
不過如果把關注點都放在我們的調用類上面,利用屬性注入和構造器注入我們已經實現了一個符合迪米特法則而低耦合的調用類,
這對於這個調用類來說已經足夠了。
文筆中若有疏忽之處,歡迎斧正。