一、依賴註入的概念瞭解 介紹依賴註入(DI),首先要先瞭解一個概念——即控制反轉(IoC)。 控制反轉是面向對象編程的一種設計原則,可以用來減低電腦代碼之間的耦合度。在傳統的應用程式中,都是程式員手動在類的內部創建需要依賴的對象,而這種方式經常會導致類與類之間的高度耦合,難以測試。而當有了IoC容 ...
一、依賴註入的概念瞭解
介紹依賴註入(DI),首先要先瞭解一個概念——即控制反轉(IoC)。
控制反轉是面向對象編程的一種設計原則,可以用來減低電腦代碼之間的耦合度。在傳統的應用程式中,都是程式員手動在類的內部創建需要依賴的對象,而這種方式經常會導致類與類之間的高度耦合,難以測試。而當有了IoC容器之後,類把創建和查找依賴對象的許可權都交給了容器,由容器進行註入組合對象,所以對象與對象之間是鬆散耦合,這一方便與測試,同時也利用功能的復用和代碼結構的靈活。
DI(Dependency Injection),即“依賴註入”。IoC是一個很大的概念,可以用不同的方式實現。其主要表現形式主要有兩種,一種是依賴查找,另外一種就是依賴註入了,依賴註入即組件不做定位查詢,只提供普通的Java方法讓容器去決定依賴關係。容器全權負責組件的裝配,它會把符合依賴關係的對象通過JavaBean屬性或者構造函數傳遞給需要的對象。通過JavaBean屬性註射依賴關係的做法稱為設置方法註入(Setter Injection);將依賴關係作為構造函數參數傳入的做法稱為構造器註入(Constructor Injection)。
二、依賴註入的優點、實現
在我們的實際生產中,任何一個應用都是由許多類組成,這些類相互之間按照協作來完成特定的業務邏輯。按照傳統的方式,每個對象負責管理與自己相互協作的對象(即它所依賴的對象)的引用,這將會導致高度耦合和難以測試的代碼。
舉個例子,下麵代碼中的Lecturer類,只能執行講授高等數學課的任務
public class Lecturer implements Teacher { private AdvancedMathematicsTask task; public Lecturer() { // 在這裡與 MathLessonQeust緊耦合 this.task = new AdvancedMathematicsTask(); } public void lectureLesson() { task.lecture(); } }
可以從上面的代碼中看出,lecturer在構造函數中創建了AdvancedMathematicsTask,這使得Lecturer和AdvancedMathematicsTask緊密地耦合到了一次,因此極大地限制了這個講師的授課能力,如果這學期需要以為講授高等數學的老師,那麼這位講師可以立刻就任,都是如果開一門線性代數,或者統計學,那麼這位講師就愛莫能助了。
更加糟糕的是,為這個Lecturer編寫單元測試的時候將格外的困難。在這樣的代碼中,你必須保證當講師的lectureLesson()方法被調用的時候,task的lecture()方法也要被調用。但是沒有一個簡單明瞭的方式能夠實現這一點。很遺憾,Lecturer將無法進行測試。
而通過DI,對象的依賴關係將由系統中負責協調各對象的第三方組件來創建對象的時候進行設定。對象無需自行創建或管理他們的依賴關係。
下麵我們通過代碼,展示這一點
public class Professor implements Teacher { private Task task; public Professor(Task task) { // Quest 被註入進來 this.task = task; } public void lectureLesson() { task.lecture(); } }
從上面代碼,我們可以看到與之間不同,Professor沒有自行創建授課任務,而是在構造器中把授課任務作為參數傳入。這是依賴註入的方式之一,即前面提到的構造器註入。
更重要的是,傳入的任務類型是Task,也就是所有授課任務都必須實現同一個介面。所以,Professor可以響應AdvancedMathematicsTask、LinearALgebraTask、StatisticsTask等任意Task的實現。
這裡的要點是Professor沒有與任何特定的Task實現發生耦合,對它來說,被要求講授的課只要實現了Task介面,那麼具體是哪門課就無所謂了。這就是DI所帶來的最大收益——松耦合。如果一個對象只通過介面(而不是具體實現或初始化過程)來表明依賴關係,那麼這種依賴就能夠在對象本身毫不知情的情況下,用不同的具體實現進行替換。
對依賴進行替換的一個最採用方法就是在測試的時候使用mock實現。我們無法充分地測試Lecturer,因為它是緊耦合的;但是可以輕鬆地測試Professor,只需要給它一個Task的mock實現即可,如下麵代碼所示
import static org.mockito.Mockito.*; import org.junit.Test; public class ProfessorText { @Test public void ProfessorShouldLectureLesson() { Task mockTask = mock(Task.class); // 創建 mock Task Professor professor = new Professor(mockTask); // 註入 mock Task professor.lectureLesson(); verify(mockTask, times(1)).lecture(); // 驗證mockTask實現的lecture()方法僅被調用一次 }
}
所以我們可以從上面的例子看出,DI能夠讓相互協作的軟體組件保持鬆散耦合。
小結
DI是組裝應用的一種方式,藉助這種方式對象無需知道依賴來自何處或者依賴的實現方式。不同於自己獲取依賴對象,對象會在運行期賦予它們所依賴的對象。依賴對象通常會通過介面瞭解所註入的對象,這樣的話就能確保低耦合。
文章參考
《spring in action》