Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。
IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。
Dependency Inversion The Dependency Inversion Principle 有清楚的解釋。
簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。
舉個例子,例如下面這個程式:
#include
....
void save() {
....
saveToFloppy()
}
}
由於save()程式依賴於依賴於saveToFloppy(),如果今天要更換低層的存儲模組為Usb碟,則這個程式沒有辦法重用,必須加以修改才行,低層模組的更動造成了高層模組也必須跟著更動,這不是一個好的設計方式,在設計上希望模組都依賴於模組的抽象,這樣才可以重用高層的業務設計。
如果以物件導向的方式來設計,依賴反轉(Dependency Inversion)的解釋變為程式不應依賴實作,而是依賴於抽象,實作必須依賴於抽象。來看看下面這個 Java 程式:
public class BusinessObject {
private FloppyWriter writer = new FloppyWriter();
....
public void save() {
...
writer.saveToFloppy();
}
}
在這個程式中,BusinessObject 的存檔依賴於實際的 FloppyWriter,如果今天想要將存檔改為存至 Usb 碟,則必須修改或繼承 BusinessObject 進行擴展,而無法直接使用BusinessObject。
如果透過介面的宣告,可以改進此一情況,例如:
public interface IDeviceWriter {
public void saveToDevice();
}
public class BusinessObject {
private IDeviceWriter writer;
public void setDeviceWriter(IDeviceWriter writer) {
this.writer = writer;
}
public void save() {
....
writer.saveToDevice();
}
}
這樣一來,BusinessObject 就是可重用的,如果今天有存儲至 Floppy 或 Usb 碟的需求,只要實作 IDeviceWriter 即可,而不用修改 BusinessObject:
public class FloppyWriter implement IDeviceWriter {
public void saveToDevice() {
....
// 實際儲存至Floppy的程式碼
}
}
public class UsbDiskWriter implement IDeviceWriter {
public void saveToDevice() {
....
// 實際儲存至UsbDisk的程式碼
}
}
從這個角度來看,Dependency Inversion 的意思即是程式不依賴於實作,而是程式與實作都要依賴於抽象。
IoC 的 Control 是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,可以獲得元件的可重用性,在上面的 Java 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的 IDeviceWriter 介面上,使得BusinessObject、FloppyWriter、UsbDiskWriter 這幾個實現依賴於抽象的 IDeviceWriter 介面。
程式的業務邏輯部份應是可以重用的,不應受到所使用框架或容器的影響,因為可能轉移整個業務邏輯至其它的框架或容器,如果業務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。
IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:"Don't call me, I'll call you." 以程式的術語來說的話,就是「不要向容器要求您所需要的(物件)資源,容器會自動將這些物件給您!」。IoC 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。