依賴注入的通俗講解,設計低耦合的系統
依賴注入的通俗講解,設計低耦合的系統
依賴注入是一種實現方式,其目的是為了構建低耦合的系統,我用一個簡單的生活中的例子來描述為什麼需要依賴注入,以及依賴注入的好處
先講一講概念:允許從類的外部注入依賴項,因此注入依賴項的類只需要知道一個協定(通常是C#介面)
這句話很抽象,我們可以拿現實中的例子來對比
概念 | 類比 |
---|---|
允許從類的外部注入依賴項 | 家裡的供電線路接入電器 |
因此注入依賴項的類只需要知道一個協定(通常是C#介面) | 不需要知道是什麼電器,只要是對應的插孔接上就能用 |
這個概念不一定好理解,我們可以先用生活的例子說明依賴注入的好處,這裡用一個廚房需要電器的例子
沒有依賴注入/沒有電源插頭
修改的生活例子 | 具體操作 |
---|---|
在廚房一角安裝一臺電飯鍋 | 將電飯鍋的電源線接入供電線,纏上絕緣膠布以防觸電或短路 |
將電飯鍋更換為微波爐 | 撕開之前纏上的絕緣膠布,拆除電飯鍋的電源線,更換為微波爐的電源線,重新纏上絕緣膠布以防觸電或短路 |
將微波爐更換為豆漿機 | 撕開之前纏上的絕緣膠布,拆除微波爐的電源線,更換為豆漿機的電源線,重新纏上絕緣膠布以防觸電或短路 |
測試的生活例子 | 具體操作 |
---|---|
測試不工作的豆漿機是供電線問題還是豆漿機問題 | 撕開之前纏上的絕緣膠布,拆除豆漿機的電源線,更換為其他電器的電源線,重新纏上絕緣膠布以防觸電或短路,工作則是豆漿機問題,不工作則是供電線問題 |
是的,就是這麼麻煩,修改一次需要電工過來,需要安裝電器裝置的過來,一起折騰一兩個小時的時間,當需求變更,沒有依賴注入/沒有電源插頭就是這麼麻煩,所以我們總是要加班,加不完的班
但是如果有插頭/依賴注入呢
擁有插頭/依賴注入
修改的生活例子 | 具體操作 |
---|---|
將電飯鍋換為微波爐 | 取下電飯鍋的插頭,插上微波爐的插頭 |
將微波爐換為豆漿機 | 取下微波爐的插頭,插上豆漿機的插頭 |
測試的生活例子 | 具體操作 |
---|---|
測試不工作的豆漿機是供電線問題還是豆漿機問題 | 取下豆漿機的插頭,插上一個其他正常電器,工作則是豆漿機問題,不工作則是供電線問題 |
此時問題就變得很簡單,只需要安裝電器裝置的簡單操作一下就解決了問題
這個問題換到軟體開發也是一樣的,如果沒有插頭和插座(介面),問題就變得異常複雜,實際上在軟體開發過程中,真的有很多人不用插頭和插座(介面)
class Boiler
{
//這個方法可以簡寫為
// public string Work() => "我是鍋我在燒水";
public string Work()
{
return "我是鍋我在燒水";
}
}
class Power
{
public string DoWork()
{
Boiler boiler = new Boiler();
return boiler.Work();
}
}
class Program
{
static void Main(string[] args)
{
Power power = new Power();
Console.WriteLine(power.DoWork());
}
}
這就是一個沒有插頭的例子,在Power
這個供電線上,直接接上了鍋,如果要有需求變更,則需要在供電線(Power類)上更改DoWork
方法,同時在測試中,如果出現了問題,也很難定位到問題是處在Power
還是Boiler
這只是一個簡單的示例,只在一個地方用到了Boiler
可能看起來好像也只需要更改一點點,但是在大型專案中,可能會幾十次幾百次的用到,那就費時費力了,要是有哪個地方沒改到,問題就更嚴重了
於是在軟體開發中有了依賴注入這一概念
interface IWork
{
string Work();
}
class Boiler:IWork
{
//這個方法可以簡寫為
// public string Work() => "我是鍋我在燒水";
public string Work()
{
return "我是鍋我在燒水";
}
}
class Power
{
private readonly IWork _work;
public Power(IWork work)
{
//將work形參賦值給_work欄位,如果work形參是null則丟擲異常
_work = work ?? throw new ArgumentNullException(nameof(work)); ;
}
public string DoWork()
{
return _work.Work();
}
}
class Program
{
static void Main(string[] args)
{
Power power = new Power(new Boiler());
Console.WriteLine(power.DoWork());
}
}
這個程式碼便是最基本的依賴注入,在這份程式碼中,首先定義了一個IWork
的插頭,Boiler
滿足IWork
的插頭要求
在Power
中,設計了一個_work
插座,接收IWork
插頭,在使用時,Power
供電給這個插頭就行了
在這個設計中,如果需要將鍋更換為其他電器,只需要在構造Power物件時傳遞實現了這個插頭的其他類即可,無需再改動Power
中的程式碼
還是一個表格對比差異
沒有使用依賴注入
修改的需求 | 具體實現 |
---|---|
A類呼叫B類的例項方法 | 在當前類中new一個物件執行 |
更換A中呼叫的B類的例項方法為C類的例項方法 | 修改A類中所有有關B類的引用為C類的引用(可能包含成百上千的引用,一旦漏掉就是嚴重的BUG) |
更換A中呼叫的C類的例項方法為D類的例項方法 | 修改A類中所有有關C類的引用為D類的引用(可能包含成百上千的引用,一旦漏掉就是嚴重的BUG) |
測試的需求 | 現實 |
---|---|
A類工作出現嚴重異常 | 無法準確定位A類錯誤還是其呼叫的底層類錯誤 |
使用依賴注入
修改的需求 | 具體實現 |
---|---|
A類呼叫B類的例項方法 | 建立一個介面,A類中呼叫符合介面的方法,構造A類物件時將B類的例項物件傳遞過去 |
更換A中呼叫的B類的例項方法為C類的例項方法 | 更改A類物件構造引數中B類例項物件為C類例項物件 |
更換A中呼叫的C類的例項方法為D類的例項方法 | 更改A類物件構造引數中C類例項物件為D類例項物件 |
測試的需求 | 現實 |
---|---|
A類工作出現嚴重異常 | 編寫簡單的、不出錯的簡單類傳遞給A類測試,若A類正常則是底層類錯誤 |
使用依賴注入的好處是顯而易見的,尤其是在需求頻繁變更的時候。
0x02 IOC容器(DI容器)
依賴注入很好,但是大型專案中成百上千的依賴如果需要手動注入依然很麻煩,於是有了DI容器(IOC容器)
- IOC:為相互依賴的元件提供抽象,將依賴(低層模組)物件的獲得交給第三方(系統)來控制,即依賴物件不再被依賴模組的類中直接通過new來獲取
在使用依賴注入時就是使用了IOC設計原理,所以我們說的DI容器其實和IOC容器是一個東西,雖然他們之間的概念有一點差別
IOC容器的主要功能:
- 動態建立、注入依賴物件
- 管理物件的生命週期
- 對映依賴關係