IoC模式

 

1引言

    面向對象的思想已經深入人心,但是要利用面向對象的思想開發出優秀的應用程序卻不是一件容易的事情。正是基於面向對象的思想,人們對各種應用程序進行了大量的分析、總結、歸納出了設計模式。Alexanders給出模式的經典定義是:每個模式都描述了一個在我們的環境中不斷出現的問題,然後描述了該問題的解決方案的核心。通過這種方式,你可以無數次地使用那些已有的解決方案,無需再重複相同的工作[2]。設計模式技術在GoF的經典書籍面世以來,得到了廣泛的關注、研究與應用。時下如雨後春筍般涌現的各種框架都是利用設計模式的典範。其中IoC模式更是廣泛應用於各種框架中。

2 IoC模式

2.1 IoC模式簡介

    IoC(Inversion of Control)模式並不是什麼新的東西,它是一種很普遍的概念,GoF中的Template Method 就是IoC的結構。顧名思義,IoC即控制反轉。著名的好萊塢原則:“Don’t Call us, We will call you”,以及Robert C. Martin在其敏捷軟件開發中所描述的依賴倒置原則(Dependency Inversion Principle, DIP)都是這一思想的體現。依賴注入(Dependency Injection)是Martin Flower對IoC模式的一種擴展的解釋[2]。IoC是一種用來解決組件(實際上也可以是簡單的Java類)之間依賴關係、配置及生命週期的設計模式,其中對組件依賴關係的處理是IoC的精華部分。IoC的實際意義就是把組件之間的依賴關係提取(反轉)出來,由容器來具體配置。這樣,各個組件之間就不存在hard-code的關聯,任何組件都可以最大程度的得到重用。運用了IoC模式後我們不再需要自己管理組件之間的依賴關係,只需要聲明由容器去實現這種依賴關係。就好像把對組件之間依賴關係的控制進行了倒置,不再由組件自己來建立這種依賴關係而交給容器(例如我們後面會介紹的PicoContainer、Spring)去管理。

    我們從一個簡單的例子看起,考慮一個Button控制Lamp的例子:

public class Button {

    private Lamp lamp;

    public void push() {

        lamp.turnOn();

    }

}

    但是馬上發現這個設計的問題,Button類直接依賴於Lamp類,這個依賴關係意味着當Lamp類修改時,Button類會受到影響。此外,想重用Button類來控制類似與Lamp的(比如同樣具有turnOn功能的Computer)另外一個對象則是不可能的。即Button控制Lamp,並且只能控制Lamp。顯然違反了“高層模塊不應該依賴於低層模塊,兩者都應該依賴於抽象;抽象不應該依賴於具體實現,細節應該依賴於抽象” 這一原則(DIP原則)。考慮到上述問題,自然的想到應該抽象出一個接口SwitchableDevice,來消除Button對Lamp的依賴,於是設計如下:

public class Button {

    private SwitchableDevice lamp;

    public Button(){

    lamp= new Lamp();

    }

}

    再深入考慮一下,雖然我們的Button現在可以控制實現了SwitchableDevice接口的Computer,但是Button和Lamp類之間還是存在create這樣的依賴關係。爲了解決這種依賴關心,經典的GoF模式就是採用Factory模式,將對象的創建交給Factory類來創建,但是這種創建仍是顯示的,組件變化了仍然需要重新編譯程序。而採用J2EE經典的service locator模式,如果你要把Button組件拿到另一個系統裏面用,你就必須修改它的源碼,讓它使用另一個系統的serviceLocator。換句話說,這個組件不具備可移植性。這就是需要依賴注入的道理,讓組件的創建、配置及生命週期總是由外部容器來管理。

 

2.2 IoC的類型

2.2.1 IoC的類型

    在介紹如何利用IoC模式實現徹底解耦之前,我們先看看IoC的類型:

2.2.1.1 Method-based (M) IoC

    在每個方法調用中傳遞其依賴的組件。如果方法需要某個組件,就把該組件作爲參數傳遞給方法。

2.2.1.2 Interface-based (I) IoC (Type 1)

    使用接口如Serviceable, Configurable 等等,來聲明依賴。EJB容器就是一個Type1的重量級容器,部署在它內部的EJB組件使用接口來聲明依賴關係。

2.2.1.3 Setter-based (S) IoC (Type 2)

    使用setters 來設置依賴組件。把依賴的組件作爲一個屬性,通過setters方法來動態設置依賴組件。

2.2.1.4 Constructor-based (C) IoC (Type 3)

    使用構造函數來聲明依賴。通過傳遞組件參數到構造函數中,來實現依賴關係。

 

2.2.2 IoC類型的比較

    這幾種類型中,type 3侵入性較小。因爲在面向對象的理論裏,constructor並不是對象契約的一部分。按照Bertrand Meyer的說法,你永遠不應該直接調用constructor,因爲這就意味着client代碼與實現(而非契約)綁定在一起。那麼,既然constructor並不屬於對象契約的一部分,在constructor裏暴露元信息就不會影響對象契約。Type 2雖然也很好,但setter畢竟屬於對象契約,把一個setter用於IoC多少有一點“破壞性”,而且通過setter方法過多的暴露了內部對象的內部細節,這就失去了對象的封裝。

    Type 2很合適的作爲應用程序的bean工廠。如果是更多的動態組裝,可能type 3更好一點。從定義上來說,type 2是基於setter的,type 3是基於constructor的。爲什麼說type 2更適合於做bean工廠呢?因爲setter是各個分離的,對於有定義的n個setter,bean工廠調用其中的0~n個都是合法的。而type 3則稍微有點麻煩,不能適應依賴較多的情況,組件的“元信息”在constructor的參數列表中體現,你必須一次性提供所有必要的參數。如果需要很多組件,就需要在構造函數中傳遞很多參數,這樣會導致constructor的參數過多過長。

 

2.3 IoC容器

    根據容器對組件的侵入的程度,可以把IoC容器分爲以下三類:

2.3.1 Interface Injection

    對應Type 1 IoC ,使用接口來聲明依賴。這類IoC容器侵入性最強,需要通過上下文來獲取組件.組件需要實現容器提供的特定接口,這樣,組件的重用就被限定在該容器內。這類容器的代表有Apache Avalon。Avalon 不怎麼流行,儘管它很強大而且有很長的歷史。Avalon屬於重量級容器,並且看起來比新的IoC解決方案更具侵入性。

 

2.3.2 Setter Injection

    對應Type 2 IoC ,使用setters來設置依賴組件。這類IoC容器需要組件提供accessor方法,依賴關係通過setter方法來注入。按照java組件模型,一般的javabean都會有accessor方法,因此組件的重用性沒有任何限制。這類容器的代表有Spring,同時它也實現了第三類IoC容器。Spring是一個非常活躍的、優秀的開源項目。它是一個基於IoCAOPAspect-Oriented Programming,面向方面編程)的構架多層J2EE系統的框架,它優雅的實現了MVC框架,支持使用可聲明事務管理(declarative transaction management)。更重要的是Spring框架的無侵入性[3]

 

2.3.3 Constructor Injection

    對應Type 3 IoC ,使用構造函數來聲明依賴。這類IoC容器需要組件由構造方法來配置依賴關係。和第二種IoC類型類似,組件重用沒有任何問題。並且Constructor Injection更加嚴格,完全按照契約(contract)來配置組件依賴。這類容器的代表有PicoContainer。

PicoContainer是一個輕量級而且更強調通過構造函數表達依賴性,而不是JavaBean 屬性。 Spring不同,它的設計允許每個類型一個對象的定義(可能是因爲它拒絕任何Java代碼外的元數據導致的侷限性)

 

2.4 利用IoC容器實現控制反轉

    下面我們就來看看如何利用IoC容器PicoContainer實現本文開始處舉的例子,主要代碼如下:

private MutablePicoContainer configureContainer() {

    MutablePicoContainer pico = new DefaultPicoContainer();

    pico.registerComponentImplementation(SwitchableDevice.class, Lamp.class);

    pico.registerComponentImplementation(Button.class);

    return pico;

}

    然後就可以通過MutablePicoContainer的getComponentImplementation方法獲得實現類,調用其push方法控制Lamp的開關,這樣一來,兩者之間的耦合通過PicoContainer提供的Assembler完全消除了。

    Spring則通過一個XML格式的配置文件,將兩者聯繫起來。使用時,通過ApplicationContext獲得Button bean,再調用其方法實現,同樣也消除了耦合關係。

 

3總結

    IoC具有以下幾個優點:

    1.因爲組件不需要在運行時尋找合作者,所以他們可以更簡單的編寫和維護。由於同             樣原因,便於編寫測試代碼,使類的測試更容易。

    2.不需要外部依賴。能在任何環境下開發和測試組件,而不需要特殊的部署環境,像   JNDIEJB那樣。並且在不同IoC容器中可方便的重用和改變。

    3.整個系統更容易組裝和配置。大部分業務對象不依賴於IoC容器的APIs。這使得很  容易使用遺留下來的代碼,且很容易的使用對象,無論在容器內或不在容器內。

    4.增加組件的複用程度,提供軟件生成效率。

    當然,IoC與通常的方法相比,代碼不便於理解,因爲組件創建是隱含的。所以輕量級的、無侵入性的IoC容器仍然有待我們去研究開發。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章