IoC與DI
IoC和DI是Spring的兩個核心概念,很多人都把它們視爲相同的東西,但事實並非如此。
IoC(Inversion of Control):控制反轉。
DI(Dependency Injection):依賴注入。
爲了方便理解,先給出結論:
控制反轉是目的,依賴注入是實現控制反轉的手段。
控制反轉是一種面向對象的思想,它是一種寬泛的概念,只要一個類將對它內部狀態的控制權交由其他機制去完成即爲『控制反轉』。控制反轉是爲了降低類與類之間的耦合度。
而Spring採用依賴注入這一具體的手段來達到控制反轉的目的。
依賴注入詳解
一個類內部往往有很多成員變量,如:
class A {
private Person chaimm;
}
- 1
- 2
- 3
上述代碼在面向對象中可以描述爲:
- A類和Person類之間存在依賴關係;
- A依賴於Person;
- A爲依賴類;
- Perosn爲被依賴類;
通常情況下,依賴類需要自己去創建並維護被依賴類的對象,如:
class A {
private Person chaimm = new Person();
}
- 1
- 2
- 3
但依賴注入的做法是:將被依賴對象的創建與維護工作交由專門的機構,而依賴類中只需要聲明所需要的成員變量。
也就是說,依賴類原本需要主動去獲取對象,但採用依賴注入後對象由第三方機構提供,自己僅需聲明需要什麼對象即可。
這樣做的目的就是爲了降低兩個類之間的耦合程度。
PS:在Spring中,那個創建、管理對象的機構就稱爲『IoC Service Provider』。
但此時還沒體現出依賴注入能降低耦合度這一點,只有當依賴注入與面向接口編程結合起來,才能真正發揮依賴注入的優勢。接下來先介紹一下『面向接口編程』。
什麼是面向接口編程?
一個類依賴其他類的目的是爲了獲取其他類所提供的服務,可能這種服務有多種實現,我們可能需要根據不同的場景使用不同的實現。此時,我們可以使用多態,將同一功能的多種實現抽象出一個接口,併爲所有實現定義一套相同的API。在使用時聲明接口類型的變量而非實現類的變量,並將實現類的對象賦給接口變量,最後用接口變量去調用實現類的服務,如:
class A {
private Super super = new SuperImpl_1();
public static void main ( String[] args ) {
// 使用Super提供的服務
super.method_1();
super.method_2();
super.method_3();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
這樣,當想使用SuperImpl_2提供的功能時,只需替換Super的實現類,其他地方不做任何變化:
private Super super = new SuperImpl_2();
- 1
上述過程就是面向接口編程的思想:若某一類服務有多種不同的實現,我們需要抽象出一個接口,並在接口中定義一套API。在使用時聲明接口類型變量,並用實現類的對象賦值。接下來通過接口類型的變量調用服務即可。當功能發生變化時,僅需替換實現類即可。
在面向接口編程的基礎上使用依賴注入的好處
上述過程如果要換一種實現,就必須要修改A類的代碼,再重新編譯。而使用了依賴注入後,由於依賴類不需要自己創建維護被依賴對象,該過程由IoC Service Provider完成。因此,當需要替換實現類時,只需在IoC Service Provider中修改,被依賴類、依賴類都不會受到影響,此時這兩個類是松耦合的。
依賴注入的三種方式
下面介紹三種方式,將被依賴對象注入給依賴類。
1. 構造器注入
將被依賴對象通過構造函數的參數注入給依賴對象,並且在初始化對象的時候注入。
優點:
對象初始化完成後便可獲得可使用的對象。
缺點:
1. 當需要注入的對象很多時,構造器參數列表將會很長;
2. 不夠靈活。若有多種注入方式,每種方式只需注入指定幾個依賴,那麼就需要提供多個重載的構造函數,麻煩。
2. setter方法注入
IoC Service Provider通過調用成員變量提供的setter函數將被依賴對象注入給依賴類。
優點:
靈活。可以選擇性地注入需要的對象。
缺點:
依賴對象初始化完成後由於尚未注入被依賴對象,因此還不能使用。
3. 接口注入
依賴類必須要實現指定的接口,然後實現該接口中的一個函數,該函數就是用於依賴注入。該函數的參數就是要注入的對象。
接口注入中,接口的名字、函數的名字都不重要,只要保證函數的參數是要注入的對象類型即可。
缺點:
侵入行太強,不建議使用。
PS:什麼是侵入行?
如果類A要使用別人提供的一個功能,若爲了使用這功能,需要在自己的類中增加額外的代碼,這就是侵入性。