依賴倒置原則(Dependency Inversion Principle)爲咱們提供了下降模塊間耦合度的一種思路,依賴注入(Dependency Injection)是一種具體的實施方法。html
依賴倒置原則:ide
前面一篇講軟件設計原則的文章中已經提到了「依賴倒置原則」(Dependency Inversion Principle),該原則主要是爲了下降模塊與模塊之間的「耦合度」,提倡模塊與模塊之間不要發生直接的依賴關係,即:高層模塊不該該直接依賴於低層模塊,高層模塊和低層模塊應該同時依賴一個抽象層。若是如今有一個類Manager在處理某一任務時,須要記錄錯誤日誌,那麼咱們能夠這樣編寫代碼:spa
1 class Manager 2 { 3 //… 4 FileLogger _logger; 5 public void DoSomething() 6 { 7 try 8 { 9 //…do something 10 } 11 catch(Exception ex) 12 { 13 if(_logger == null) 14 { 15 _logger = new FileLogger(); 16 } 17 _logger.Log(ex.ToString()) 18 } 19 } 20 } 21 class FileLogger 22 { 23 public void Log(string errorLog) 24 { 25 //…write into log file 26 } 27 }
如上代碼所示,FileLogger類負責將錯誤日誌保存到文件,Manager類中定義了一個Logger類對象,專門負責記錄錯誤日誌,這段代碼中的「高層模塊」Manager類就直接依賴與「低層模塊」FileLogger,若是咱們如今須要將錯誤日誌記錄經過Email發送給別人,或者發送給別的模塊,咱們不得不去修改Manager類的代碼。設計
「依賴倒置原則」建議咱們,Manager類不該該直接依賴於FIleLogger類,而應該依賴一個抽象層(接口層),因此原來代碼應該這樣寫:日誌
1 class Manager 2 { 3 ILog _logger; 4 public void DoSomething() 5 { 6 try 7 { 8 9 } 10 catch(Exception ex) 11 { 12 if(_logger == null) 13 { 14 _logger = new FileLogger(); 15 // _logger = new EmailLogger(); 16 //_logger = new NotifyLogger(); 17 } 18 _logger.Log(ex.ToString()); 19 } 20 } 21 } 22 interface ILog 23 { 24 void Log(string errorLog); 25 } 26 class FileLogger:ILog 27 { 28 public void Log(string errorLog) 29 { 30 //…write into file 31 } 32 } 33 class EmailLogger:ILog 34 { 35 public void Log(string errorLog) 36 { 37 //…send to others as email 38 } 39 } 40 class NotifyLogger:ILog 41 { 42 public void Log(string errorLog) 43 { 44 //… notify other modules 45 } 46 }
如上代碼所示,咱們把記錄錯誤日誌的邏輯抽象出來一個ILog接口,Manager類再也不依賴於任何一個具體的類,而是依賴於ILog接口,同時咱們能夠根據ILog接口實現各類各樣的日誌記錄類,如FileLogger將日誌保存到文件、EmailLogger將日誌以郵件形式發送給別人、NotifyLogger將錯誤信息通知程序中其餘模塊。這樣以來,整個代碼的靈活度明顯增長了,若是咱們須要將日誌保存到文件,直接使用FileLogger,若是咱們想將日誌以郵件形式發送別人,直接使用EmailLogger等等。下圖顯示依賴倒置發生先後:code
依賴注入:htm
上面的Manager類雖然再也不直接依賴任何具體的日誌記錄類型,可是實質上,咱們建立記錄日誌類對象仍是在Manager內部(catch中),若是咱們想換種方式記錄日誌,仍是得動Manager類的代碼,有沒有一種方式,可以讓咱們不須要修改Manager代碼就能切換日誌的記錄方式呢?固然是有的,「依賴注入」就是這一問題的具體解決方法,咱們有三種方式去讓兩個類型發生依賴關係:對象
(1)構造注入(Constructor Injection)blog
在咱們建立Manager對象的時候,將記錄日誌的對象做爲構造參數傳遞給新建立的Manager對象,假設Manager有一個帶ILog類型參數的構造方法,如:接口
1 class Manager 2 { 3 ILog _logger; 4 public Manager(ILog logger) 5 { 6 _logger = logger; 7 } 8 //… 9 }
那麼,咱們在建立Manager對象的時候,這樣編寫代碼:
Manager m = new Manager(new FileLogger());
//Manager m = new Manager(new EmailLogger());
//Manager m = new Manager(new NotifyLogger());
很明顯,這種日誌記錄方式一直不變,對Manager終生有效。
(2)方法注入(Method Injection)
爲Manager類中每一個須要記錄日誌的方法增長一個ILog的參數,好比Manager.DoSomething方法從新定義爲:
1 class Manager 2 { 3 //… 4 public void DoSomething(ILog logger) 5 { 6 try 7 { 8 //… 9 } 10 catch(Exception ex) 11 { 12 logger.Log(ex.ToString()); 13 } 14 } 15 }
那麼咱們以後在使用Manager的時候,每次調用方法都應該爲它提供一個記錄日誌的對象,如:
Manager m = new Manager();
m.DoSomething(new FileLogger());
m.DoSomething(new EmailLogger());
m.DoSomething(new NotifyLogger());
這種記錄日誌的方式,只對當前方法有效,每次調用方法均可以不一樣。
(3)屬性注入(Property Injection)
在Manager類中公開一個屬性,用來設置日誌記錄對象,Mananger這樣定義:
1 class Manager 2 { 3 private ILog _logger; 4 public ILog Logger 5 { 6 get 7 { 8 return _logger; 9 } 10 set 11 { 12 _logger = value; 13 } 14 } 15 //… 16 }
以後咱們使用Mananger時,能夠隨時更換它的日誌記錄方式:
Mananger m = new Manager();
m.Logger = new FileLogger();
m.Logger = new EmailLogger();
m.Logger = new NotifyLogger();
使用這種方式,咱們能夠隨時切換記錄日誌的方式,它的靈活度介於「構造注入」和「方法注入」之間。
以上三種依賴注入方法能夠混合使用,也就是說,你能夠爲Manager類定義一個帶ILog類型的參數,同時也能夠定義一個ILog類型的屬性,或者爲每一個方法增長一個ILog類型的參數。
注:
【1】在.NET中,「抽象層」能夠不使用接口interface去實現,而是直接使用委託,舉一個例子,咱們使用FileStream.BeginRead方法時,給它提供的一個AsyncCallback回調參數,其實就是屬於「方法注入」的一種。
【2】類型與類型之間不可能徹底失去依賴關係,怎樣讓這種非有不可的依賴關係更微弱,是軟件設計的一門高深學問。
上一篇設計原則(倒數第二小節) http://www.cnblogs.com/xiaozhi_5638/p/3610706.html