依賴注入和控制反轉到底是什麼意思?
控制反轉(Ioc):調用者不再創建被調用者的實例,由IOC容器框架創建(C#中常用的IOC框架有Autofac/Unity等等),這種方式稱爲控制反轉
依賴注入(DI):容器框架將創建好的實例注入到調用者稱爲依賴注入
依賴倒置原則(DIP)
高層模塊不應依賴於低層模塊,兩者應該依賴於抽象。
抽象不不應該依賴於實現,實現應該依賴於抽象。
使用Ioc的好處是什麼?
控制反轉是面向對象編程中的一種設計原則,可以用來降低代碼之間的耦合度,Ioc把創建和查找依賴對象的控制權交給容器,由容器進行注入,所以對象與對象之間是鬆散耦合,傳統的應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間是緊耦合
舉例
class SqlDal { public void Add() { Console.WriteLine("向SQLServer數據庫添加一條新訂單"); } } class Order { SqlDal sqlDal = new SqlDal(); public void Add() { sqlDal.Add(); } }
首先定義了SqlDal類用於SqlServer數據庫的讀寫操作,再定義Order類負責訂單的邏輯處理,因爲訂單數據是要插入到數據庫的,所以在Order類中定了SqlDal類的變量並初始化.這裏Order是依賴於SqlDal類的
控制檯調用
static void Main(string[] args) { Order order = new Order(); order.Add(); Console.ReadKey(); }
這是傳統開發中比較常見的方式,如果這時候數據庫改爲了mysql,那麼SqlDal是無法繼續使用了,需要重新定義mysqlDal,負責mysql數據庫的讀寫操作
class MySqlDal { public void Add() { Console.WriteLine("向MySql數據庫添加一條新訂單"); } }
由於Order類中直接引用了SqlDal類的對象,所以也需要同步修改爲MySqlDal
class Order { MySqlDal mySqlDal = new MySqlDal(); public void Add() { mySqlDal.Add(); } }
OK,可以滿足當前需求了,但是如果後期類似的情況再次出現,就意味着需要將剛纔的操作重新做一次,顯然這不是一個良好的設計,類與類之間是高度耦合的,可擴展性較差,它違背了DIP原則,高層模塊Order不應依賴於底層模塊SqlDal、MySqlDal,兩者應該依賴於抽象
改寫代碼
public interface IDal { void Add(); } class SqlDal : IDal { public void Add() { Console.WriteLine("向SQLServer數據庫添加一條新訂單"); } } class Order { private IDal _dal; /// <summary> /// 構造函數注入 /// </summary> /// <param name="dal"></param> public Order(IDal dal) { _dal = dal; } public void Add() { _dal.Add(); } }
第一步:定義接口IDal,添加方法Add()
第二步:定義SqlDal繼承IDal接口並實現接口中的Add()方法
第三步:在Order類中添加私有變量用於存儲抽象並在構造函數中傳遞具體依賴對象
控制檯調用
static void Main(string[] args) { Order order = new Order(new SqlDal()); order.Add(); Console.ReadKey(); }
class MySqlDal : IDal { public void Add() { Console.WriteLine("向MySql數據庫添加一條新訂單"); } }
Order order = new Order(new MySqlDal()); order.Add();
輸出結果是一樣的,但是這裏將依賴對象SqlDal對象的創建和綁定轉移到了Order外部來實現,這就解除了Order與SqlDal類的耦合關係,如果現在出現需要更改數據庫爲MySql的需求,只需要定義MySqlDal類繼承IDal並實現Add()方法,然後外部綁定依賴,不需要修改Order類內部的代碼即可實現替換,很明顯這種方式程序的可擴展性更高
屬性注入
上面所示的方式是在Order類的構造函數中注入依賴對象,還可以使用屬性注入的方式,顧名思義,屬性注入是通過屬性來傳遞依賴,因此在Order類中定義一個屬性
class Order { private IDal _dal; /// <summary> /// 屬性注入 /// </summary> public IDal Dal { get { return _dal; } set { _dal = value; } }
public void Add() { _dal.Add(); } }
在控制檯調用時給屬性賦值從而傳遞依賴
static void Main(string[] args) { Order order = new Order(); order.Dal = new SqlDal(); order.Add(); Console.ReadKey(); }
接口注入
相比構造函數注入和屬性注入,接口注入顯得有些複雜,使用也不常見。具體思路是先定義一個接口,包含一個設置依賴的方法。然後依賴類,繼承並實現這個接口
public interface IDependent { void SetDepend(IDal idal); }
class Order: IDependent { private IDal _dal; public void Add() { _dal.Add(); } public void SetDepend(IDal idal) { _dal = idal; } }
通過SetDepend方法傳遞依賴
static void Main(string[] args) { Order order = new Order(); order.SetDepend(new SqlDal()); order.Add(); Console.ReadKey(); }
Ioc容器
前面所有的例子中,我們都是通過手動的方式來創建依賴對象,並將引用傳遞給被依賴模塊,對於大型項目來說,相互依賴的組件比較多。如果還用手動的方式自己來創建和注入依賴的話,顯然效率很低,而且往往還會出現不可控的場面。正因如此,Ioc容器誕生了。Ioc容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能
- 動態創建、注入依賴對象
- 管理對象生命週期
- 映射依賴關係
總結:DIP是軟件設計的一種思想,Ioc則是基於DIP衍生出的一種軟件設計模式。DI是Ioc的具體實現方式之一,使用最爲廣泛。Ioc容器是DI構造函注入的框架,它管理着依賴項的生命週期以及映射關係