依賴倒置(DIP)與依賴注入(DI)


  依賴倒置原則(Dependency Inversion Principle)為我們提供了降低模塊間耦合度的一種思路,依賴注入(Dependency Injection)是一種具體的實施方法。

依賴倒置原則:

  前面一篇講軟件設計原則的文章中已經提到了“依賴倒置原則”(Dependency Inversion Principle),該原則主要是為了降低模塊與模塊之間的“耦合度”,提倡模塊與模塊之間不要發生直接的依賴關系,即:高層模塊不應該直接依賴於低層模塊,高層模塊和低層模塊應該同時依賴一個抽象層。如果現在有一個類Manager在處理某一任務時,需要記錄錯誤日志,那么我們可以這樣編寫代碼:

 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 }
View Code

如上代碼所示,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 }
View Code

如上代碼所示,我們把記錄錯誤日志的邏輯抽象出來一個ILog接口,Manager類不再依賴於任何一個具體的類,而是依賴於ILog接口,同時我們可以根據ILog接口實現各種各樣的日志記錄類,如FileLogger將日志保存到文件、EmailLogger將日志以郵件形式發送給別人、NotifyLogger將錯誤信息通知程序中其他模塊。這樣以來,整個代碼的靈活度明顯增加了,如果我們需要將日志保存到文件,直接使用FileLogger,如果我們想將日志以郵件形式發送別人,直接使用EmailLogger等等。下圖顯示依賴倒置發生前后:

 

依賴注入:

  上面的Manager類雖然不再直接依賴任何具體的日志記錄類型,但是實質上,我們創建記錄日志類對象還是在Manager內部(catch中),如果我們想換種方式記錄日志,還是得動Manager類的代碼,有沒有一種方式,能夠讓我們不需要修改Manager代碼就能切換日志的記錄方式呢?當然是有的,“依賴注入”就是這一問題的具體解決方法,我們有三種方式去讓兩個類型發生依賴關系:

(1)構造注入(Constructor Injection)

  在我們創建Manager對象的時候,將記錄日志的對象作為構造參數傳遞給新創建的Manager對象,假設Manager有一個帶ILog類型參數的構造方法,如:

1 class Manager
2 {
3     ILog _logger;
4     public Manager(ILog logger)
5     {
6         _logger = logger;
7     }
8     //
9 }
View Code

那么,我們在創建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 }
View Code

那么我們之后在使用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 }
View Code

之后我們使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM