我曾想深入瞭解的:依賴倒置、控制反轉、依賴注入
大道至簡
我們在軟體工程中進行的架構設計、模組實現、編碼等工作,很多時候說到底就是圍繞一件事進行:解耦。
三層架構,MVC,微服務,DDD.我們分析問題,抽象問題,然後劃分邊界,劃分層次。
也是為了讓我們的類、模組、系統有更強的複用能力,提高生產效率。
這一次,我想深入瞭解和探討我曾經很迷糊,也沒有一直仔細瞭解的:依賴倒置、控制反轉、依賴注入 這些概念。
什麼是依賴?
通常可以理解為一種需要,需求。需要協助才能完成一件事情。
例如,我們依賴日誌服務寫日誌:
public class Contract { public void Successed() { string msg = "save, successed!"; Log log = new Log(); log.Write(msg); } }
Contract類正依賴Log類,協助完成整個業務流程。這就產生了依賴。
什麼是抽象? 什麼是細節?
我們經常會聽說,面向介面程式設計,依賴於抽象不能依賴於具體實現細節。
我們每次修改介面時候,一定會去修改具體實現。但是我們修改具體實現卻很少修改介面。
所以介面比具體實現更穩定。
此時,我們在中間加入一層介面,看看如何。
public interface ILog
{
void Write();
}
public void Successed() { string msg = "save, successed!"; ILog log = new Log(); log.Write(msg ); }
關係變化如圖:
什麼是上層模組? 什麼是底層模組?
此時,Contract類可以看做上層。Log看做底層。
上層模組:指揮控制
底層模組:策略實現
依賴倒置
理清楚了 上層、底層、細節、抽象、依賴概念,
我們不難發現,上面的依賴箭頭髮生了改變。
所以依賴倒置也由此而來:
上層模組不應該依賴底層模組,它們都應該依賴於抽象。
抽象不應該依賴於細節,細節應該依賴於抽象。
依賴倒置,使得我們的擴充套件性增強。
public class Log:ILog
public class NLog : ILog
public class Log4 : ILog
// ILog log = new Log(); // ILog log = new Log4(); ILog log = new NLog(); log.Write(msg);
以上程式碼我們也可以看出,我們需要不斷註釋修改Contract類,以至於引用不同的Log元件來應對需求。
每次都要修改這個類來滿足需求(修改關閉,擴充套件開放原則),顯然是我們所不希望的。造成這種現象的原因是:
因為對於上層的Contract類,不僅僅負責業務邏輯的實現,第二職責還要負責日誌例項的構造。
對於Program類,有日誌服務類直接拿來使用即可,不需要關心這些例項的構造。
有沒有一種機制能夠將構造和使用進行分離?使得Contract的職責更加單一,耦合更低?(單一職責原則)
控制反轉
怎麼算是控制反轉了呢?
我們改一下上面的程式碼將日誌類的例項化控制權,轉移到類的外部:
public class Contract
{
public Contract(ILog log)
}
呼叫
class Program
{
static void Main(string[] args)
{
ILog log = new NLog();
Contract contract = new Contract(log);
contract.Successed();
}
}
這樣,無論外部日誌元件如何變化,都不用會影響現有的Contract類。
Contract只專注於屬於自己的職責。上層Contract類和日誌類解耦更加徹底。互不影響。
如果從職責角度來看,我們是不是可以有一個類專門來管理建立日誌類呢?
就像倉庫管理員一樣,根據單子出貨,不需要關心這些貨物到底如何被使用的。
public static class Ioc
{
public static ILog GetLogInstance(int type)
{
switch (type)
{
case 1: return new Log();
case 2: return new Log4();
case 3: return new NLog();
default: return new Log();
}
}
}
class Program
{
static void Main(string[] args)
{
ILog log = Ioc.GetLogInstance(1);
Contract contract = new Contract(log);
contract.Successed();
}
}
依賴注入
什麼是依賴注入呢?
其實我們剛剛已經實現過了,全文先是依賴倒置,然後控制反轉,而現在說的依賴注入是控制反轉的具體實現方式。
依賴注入是解開依賴並實現反轉的一種手段。
大約分為三種方式:
- 建構函式方式
public class Contract
{
private ILog _log { get; set; }
public Contract(ILog log)
{
_log = log;
}
}
優點: 構造Contract就確定好依賴。
缺點:後期無法更改依賴。
- Set方式注入
public class Contract
{
private ILog _log { get; set; }
public Contract(ILog log)
{
_log = log;
}
public void Successed()
{
string msg = "save, successed!";
_log.Write(msg);
}
public void SetLogInstance(ILog log)
{
_log = log;
}
}
優點: 將Log例項化延遲,Contract類可以靈活變動依賴。
缺點:使用_log前需要判斷null情況
- 介面方式注入
public interface ILogSetter
{
ILog Setter(ILog log);
}
public class Contract: ILogSetter
{
... ...
public ILog Setter(ILog log)
{
_log = log;
return _log;
}
}
介面方式和方式二有點類似,這裡將依賴注入提升為一種能力,可以支配依賴關係的能力。
探討 控制反轉
從這個圖中,可以看到依賴的倒置,這裡低層定義介面並繼承實現,高層引用低層定義的介面進行呼叫使用。
那麼現在的控制權是在低層,那麼定義介面這個權力到底屬於誰?
比如我們有一個流程對應著5個步驟,這5個步驟又對應著5個介面:A1,A2,A3,A4,A5
業務系統a,需要使用這個流程就需要依次呼叫這5個介面:A1 -> A2 -> A3 -> A4 -> A5
現在這個流程非常公用,已經上升成為企業級中臺的一個流程,越來越多的業務系統給都在對接。
此時,很多的業務系統需要重新用程式碼組織一套這樣的呼叫流程,當然現在的控制權還是在業務系統這裡。
所以此時我們對外公開的5個介面,只是簡單提供了呼叫能力,對於介面的編排全部寄希望於業務系統。
這個時候會有兩種聲音:
1、業務系統不想這麼繁瑣地重複著編排這些介面
2、中臺也想把流程控制權掌握在自己手中,這樣遇到業務流程的整體性變更,業務系統是不需要調整的
業務流程引擎的加入,就像是我們的介面一樣,它負責介面的編排,然後成為業務流程。
此時,控制權實際在介面提供方。
模式
我們想使用微軟提供的MVC框架,只要是遵循MVC框架的約定就能擁有MVC的能力。
MVC的控制權在框架,應用想通過框架提供的MVC能力就必須按照框架的定義去做。
如果框架僅僅是給i我們提供類似於類庫一樣的MVC實現,
那麼整個流程是應用系統自己根據文件,呼叫各種類庫檔案,編排這些實現滿足業務系統的MVC需求
所以,現在的Asp.Net Core 給我們提供的MVC,只要是我們遵循mvc約定,引擎就會推動整個資訊的流動,最終反饋給應用。
這種比較普適的流程或者方案,我們可以成為模式,類似於設計模式,MVC模式.
原來算落在業務系統中的控制權,反向轉到模式中。
總結
依賴倒置可以很小也可以很大,
控制反轉也可以很小也可以很大。
這種思想我們無時無刻可以碰到