將依賴注入引入到你的項目當中去,你將發現你的代碼顯著地變得簡潔,易懂和易於測試。
任何有實際意義的程序(頗有可能比Hello World複雜得多)都由不止一個類組成,這些類彼此交互去實現某種業務邏輯。傳統方法是,每一個對象都由它本身負責管理與之合做的對象(即它所依賴的對象)的引用。這將致使高度耦合和難以測試的代碼。
舉例來講,考慮下面這個Knight類:
java
package com.springinaction.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { quest = new RescueDamselQuest(); } public void embarkOnQuest() throws QuestException { quest.embark(); } }
想必你看到了,DamselRescuingKnight在它的構造函數中自行建立了指示RescueDamselQuest。這使得DamselRescuingKnight被緊密地和RescueDamselQuest耦合到了一塊兒,並所以極大地限制了這個騎士執行指示的能力。若是一個少女須要救援,這個騎士可以召之即來。可是若是要一條惡龍鬚要殺掉,或者一個圓桌須要……額……滾起來,那麼這個騎士只能袖手旁觀了。
更糟糕的是,爲這個DamselRescuingKnight編寫單元測試將出奇地困難。在這個測試當中,你須要保證當騎士的embarkOnQuest()被調用的時候,指示的embark()也要被調用。可是沒有一個簡單明瞭的方式,可以實現這一點。因此不幸地,DamselRescuingKnight將是一個待測試的類。 spring
耦合是個兩難的問題(two-headed beast)。一方面,緊密耦合的代碼難以測試,難以複用,難以理解,而且典型地表現出「打地鼠」式的bug特性(修復一個bug,致使出現一個新的甚至更多的bug)。另外一方面,必定程度的耦合又是必須的——徹底沒有任何耦合的代碼將一事無成。爲了建功立業,不一樣的類必須以適當的方式交互。總而言之,耦合是必須的,可是應當被當心地維護。
另外一方面,經過依賴注入(DI),對象的依賴關係將由系統中負責協調各個對象的第三方組件,在建立對象的時候提供。對象無需去自行建立或管理他們的依賴關係——依賴關係將被注入到須要它們的對象當中。 爲了展現這一點,讓咱們看一看下面這個BraveKnight,這個騎士不只勇敢,並且能響應任何形式的指示。 函數
package com.springinaction.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; } public void embarkOnQuest() throws QuestException { quest.embark(); } }
正像你看到的那樣,不一樣於以前的DamselRescuingKnight,BraveKnight沒有自行建立指示,而是在構造的時候做爲構造器的參數傳入指示。這是依賴注入的形式之一,即構造器注入。
更重要的是,他被傳入的指示類型是Quest,一個全部指示都必須實現的接口。因此,BraveKnight可以響應RescueDamselQuest,SlayDragonQuest,MakeRoundTableRounderQuest 等傳給他的任何其餘的Quest的實現。
這裏的要點是BraveKnight沒有與任何特定的Quest實現發生耦合。對他來講,被要求響應的指示只要實現了Quest接口,那麼具體是哪一類型的指示就可有可無了。這就是依賴注入最大的好處——鬆耦合。若是一個對象只經過接口(而不是具體實現或初始化的過程)來標明依賴關係,那麼這種依賴就可以在對象自己絕不知情的狀況下,用不一樣的具體實現進行替換。
對依賴進行替換的最經常使用的方法之一,就是在測試的時候用mock實現。你沒法充分測試DamselRescuingKnight,由於它是緊耦合的;可是你能夠輕鬆地測試BraveKnight,只需給他一個Quest的mock實現,以下所示:
單元測試
package com.springinaction.knights; import static org.mockito.Mockito.*; import org.junit.Test; public class BraveKnightTest { @Test public void knightShouldEmbarkOnQuest() throws QuestException { Quest mockQuest = mock(Quest.class); BraveKnight knight = new BraveKnight(mockQuest); knight.embarkOnQuest(); verify(mockQuest, times(1)).embark(); } }