(转帖)Spring Gossip: Inversion of Control

在翻译一篇涉及Spring的文章,里面有个概念叫作:Inversion of Control,我觉得这篇文章解释很透。就贴在这里了。

(转自:http://caterpillar.onlyfun.net/Gossip/SpringGossip/IOC.html)

现在还没有专门的计划去学习Spring,就把它统归在“学习java”这个大的分类里,以后再转到合适的目标下。(需要完善日记重新归类的功能)

From Gossip@caterpillar

Spring Gossip: Inversion of Control

Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。

IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。

Dependency Inversion The Dependency Inversion Principle 有清楚的解釋。

簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。

舉個例子,例如下面這個程式:

#include <floppy.h>

....

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 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。