欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
IoC 容器和Dependency Injection 模式
撰文/Martin Fowler 編譯/透明

原文:http://www.martinfowler.com/articles/injection.html

Java 社群近來(lái)掀起了一陣輕量級容器的熱潮,這些容器能夠幫助開(kāi)發(fā)者將來(lái)自不同項目的組件組裝成為一個(gè)內聚的應用程序。在它們的背后有著(zhù)同一個(gè)模式,這個(gè)模式?jīng)Q定了這些容器進(jìn)行組件裝配的方式。人們用一個(gè)大而化之的名字來(lái)稱(chēng)呼這個(gè)模式:“控制反轉”( Inversion of Control,IoC)。在本文中,我將深入探索這個(gè)模式的工作原理,給它一個(gè)更能描述其特點(diǎn)的名字——“依賴(lài)注入”(Dependency Injection),并將其與“服務(wù)定位器”(Service Locator)模式作一個(gè)比較。不過(guò),這兩者之間的差異并不太重要,更重要的是:應該將組件的配置與使用分離開(kāi)——兩個(gè)模式的目標都是這個(gè)。

在企業(yè)級Java 的世界里存在一個(gè)有趣的現象:有很多人投入很多精力來(lái)研究主流J2EE 技術(shù)的替代品——自然,這大多發(fā)生在open source 社群。在很大程度上,這可以看作是開(kāi)發(fā)者對主流J2EE 技術(shù)的笨重和復雜作出的回應,但其中的確有很多極富創(chuàng )意的想法,的確提供了一些可供選擇的方案。J2EE 開(kāi)發(fā)者常遇到的一個(gè)問(wèn)題就是如何組裝不同的程序元素:如果web 控制器體系結構和數據庫接口是由不同的團隊所開(kāi)發(fā)的,彼此幾乎一無(wú)所知,你應該如何讓它們配合工作?很多框架?chē)L試過(guò)解決這個(gè)問(wèn)題,有幾個(gè)框架索性朝這個(gè)方向發(fā)展,提供了更通用的“組裝各層組件”的方案。這樣的框架通常被稱(chēng)為“輕量級容器”,PicoContainer 和Spring 都在此列中。

在這些容器背后,一些有趣的設計原則發(fā)揮著(zhù)作用。這些原則已經(jīng)超越了特定容器的范疇,甚至已經(jīng)超越了Java 平臺的范疇。在本文中,我就要初步揭示這些原則。我使用的范例是Java 代碼,但正如我的大多數文章一樣,這些原則也同樣適用于別的OO 環(huán)境,特別是.NET。

編輯

組件和服務(wù)

“裝配程序元素”,這樣的話(huà)題立即將我拖進(jìn)了一個(gè)棘手的術(shù)語(yǔ)問(wèn)題:如何區分“服務(wù)”(service)和“組件”(component)?你可以毫不費力地找出關(guān)于這兩個(gè)詞定義的長(cháng)篇大論,各種彼此矛盾的定義會(huì )讓你感受到我所處的窘境。有鑒于此,對于這兩個(gè)遭到了嚴重濫用的詞匯,我將首先說(shuō)明它們在本文中的用法。

所謂“組件”是指這樣一個(gè)軟件單元:它將被作者無(wú)法控制的其他應用程序使用,但后者不能對組件進(jìn)行修改。也就是說(shuō),使用一個(gè)組件的應用程序不能修改組件的源代碼,但可以通過(guò)作者預留的某種途徑對其進(jìn)行擴展,以改變組件的行為。

服務(wù)和組件有某種相似之處:它們都將被外部的應用程序使用。在我看來(lái),兩者之間最大的差異在于:組件是在本地使用的(例如JAR 文件、程序集、DLL、或者源碼導入);而服務(wù)是要通過(guò)——同步或異步的——遠程接口來(lái)遠程使用的(例如web service、消息系統、RPC,或者 socket)。在本文中,我將主要使用“服務(wù)”這個(gè)詞,但文中的大多數邏輯也同樣適用于本地組件。實(shí)際上,為了方便地訪(fǎng)問(wèn)遠程服務(wù),你往往需要某種本地組件框架。不過(guò),“組件或者服務(wù)”這樣一個(gè)詞組實(shí)在太麻煩了,而且“服務(wù)”這個(gè)詞當下也很流行,所以本文將用“服務(wù)”指代這兩者。

編輯

一個(gè)簡(jiǎn)單的例子

為了更好地說(shuō)明問(wèn)題,我要引入一個(gè)例子。和我以前用的所有例子一樣,這是一個(gè)超級簡(jiǎn)單的例子:它非常小,小得有點(diǎn)不夠真實(shí),但足以幫助你看清其中的道理,而不至于陷入真實(shí)例子的泥潭中無(wú)法自拔。

在這個(gè)例子中,我編寫(xiě)了一個(gè)組件,用于提供一份電影清單,清單上列出的影片都是由一位特定的導演執導的。實(shí)現這個(gè)偉大的功能只需要一個(gè)方法:

class MovieLister...public Movie[] moviesDirectedBy(String arg){List allMovies = finder.findAll();for (Iterator it = allMovies.iterator(); it.hasNext(); ){Movie movie = (Movie)it.next();if (!movie.getDirector().equals(arg)) it.remove();}return (Movie[])allMovies.toArray(new Movie[allMovies.size()]);}

你可以看到,這個(gè)功能的實(shí)現極其簡(jiǎn)單:moviesDirectedBy 方法首先請求finder(影片搜尋者)對象(我們稍后會(huì )談到這個(gè)對象)返回后者所知道的所有影片,然后遍歷finder 對象返回的清單,并返回其中由特定的某個(gè)導演執導的影片。非常簡(jiǎn)單,不過(guò)不必擔心,這只是整個(gè)例子的腳手架罷了。

我們真正想要考察的是finder對象,或者說(shuō),如何將MovieLister對象與特定的finder對象連接起來(lái)。為什么我們對這個(gè)問(wèn)題特別感興趣?因為我希望上面這個(gè)漂亮的moviesDirectedBy方法完全不依賴(lài)于影片的實(shí)際存儲方式。所以,這個(gè)方法只能引用一個(gè)finder對象,而finder對象則必須知道如何對findAll 方法作出回應。為了幫助讀者更清楚地理解,我給finder定義了一個(gè)接口:

public interface MovieFinder {List findAll();}

現在,兩個(gè)對象之間沒(méi)有什么耦合關(guān)系。但是,當我要實(shí)際尋找影片時(shí),就必須涉及到MovieFinder 的某個(gè)具體子類(lèi)。在這里,我把“涉及具體子類(lèi)”的代碼放在MovieLister 類(lèi)的構造子中。

class MovieLister...private MovieFinder finder;public MovieLister() {finder = new ColonDelimitedMovieFinder("movies1.txt");}

這個(gè)實(shí)現類(lèi)的名字就說(shuō)明:我將要從一個(gè)逗號分隔的文本文件中獲得影片列表。你不必操心具體的實(shí)現細節,只要設想這樣一個(gè)實(shí)現類(lèi)就可以了。

如果這個(gè)類(lèi)只由我自己使用,一切都沒(méi)問(wèn)題。但是,如果我的朋友嘆服于這個(gè)精彩的功能,也想使用我的程序,那又會(huì )怎么樣呢?如果他們也把影片清單保存在一個(gè)逗號分隔的文本文件中,并且也把這個(gè)文件命名為“ movie1.txt ”,那么一切還是沒(méi)問(wèn)題。如果他們只是給這個(gè)文件改改名,我也可以從一個(gè)配置文件獲得文件名,這也很容易。但是,如果他們用完全不同的方式——例如SQL 數據庫、XML 文件、web service,或者另一種格式的文本文件——來(lái)存儲影片清單呢?

在這種情況下,我們需要用另一個(gè)類(lèi)來(lái)獲取數據。由于已經(jīng)定義了MovieFinder接口,我可以不用修改moviesDirectedBy 方法。但是,我仍然需要通過(guò)某種途徑獲得合適的MovieFinder實(shí)現類(lèi)的實(shí)例。

圖1:“在MovieLister 類(lèi)中直接創(chuàng )建MovieFinder 實(shí)例”時(shí)的依賴(lài)關(guān)系


圖1 展現了這種情況下的依賴(lài)關(guān)系:MovieLister 類(lèi)既依賴(lài)于MovieFinder接口,也依賴(lài)于具體的實(shí)現類(lèi)。我們當然希望MovieLister 類(lèi)只依賴(lài)于接口,但我們要如何獲得一個(gè)MovieFinder子類(lèi)的實(shí)例呢?

在Patterns of Enterprise Application Architecture 一書(shū)中,我們把這種情況稱(chēng)為“插件”(plugin):MovieFinder的實(shí)現類(lèi)不是在編譯期連入程序之中的,因為我并不知道我的朋友會(huì )使用哪個(gè)實(shí)現類(lèi)。我們希望MovieLister 類(lèi)能夠與MovieFinder的任何實(shí)現類(lèi)配合工作,并且允許在運行期插入具體的實(shí)現類(lèi),插入動(dòng)作完全脫離我(原作者)的控制。這里的問(wèn)題就是:如何設計這個(gè)連接過(guò)程,使MovieLister 類(lèi)在不知道實(shí)現類(lèi)細節的前提下與其實(shí)例協(xié)同工作。

將這個(gè)例子推而廣之,在一個(gè)真實(shí)的系統中,我們可能有數十個(gè)服務(wù)和組件。在任何時(shí)候,我們總可以對使用組件的情形加以抽象,通過(guò)接口與具體的組件交流(如果組件并沒(méi)有設計一個(gè)接口,也可以通過(guò)適配器與之交流)。但是,如果我們希望以不同的方式部署這個(gè)系統,就需要用插件機制來(lái)處理服務(wù)之間的交互過(guò)程,這樣我們才可能在不同的部署方案中使用不同的實(shí)現。

所以,現在的核心問(wèn)題就是:如何將這些插件組合成一個(gè)應用程序?這正是新生的輕量級容器所面臨的主要問(wèn)題,而它們解決這個(gè)問(wèn)題的手段無(wú)一例外地是控制反轉(Inversion of Control)模式。

編輯

控制反轉

幾位輕量級容器的作者曾驕傲地對我說(shuō):這些容器非常有用,因為它們實(shí)現了“控制反轉”。這樣的說(shuō)辭讓我深感迷惑:控制反轉是框架所共有的特征,如果僅僅因為使用了控制反轉就認為這些輕量級容器與眾不同,就好象在說(shuō)“我的轎車(chē)是與眾不同的,因為它有四個(gè)輪子”。

問(wèn)題的關(guān)鍵在于:它們反轉了哪方面的控制?我第一次接觸到的控制反轉針對的是用戶(hù)界面的主控權。早期的用戶(hù)界面是完全由應用程序來(lái)控制的,你預先設計一系列命令,例如“輸入姓名”、“輸入地址”等,應用程序逐條輸出提示信息,并取回用戶(hù)的響應。而在圖形用戶(hù)界面環(huán)境下,UI 框架將負責執行一個(gè)主循環(huán),你的應用程序只需為屏幕的各個(gè)區域提供事件處理函數即可。

在這里,程序的主控權發(fā)生了反轉:從應用程序移到了框架。

對于這些新生的容器,它們反轉的是“如何定位插件的具體實(shí)現”。在前面那個(gè)簡(jiǎn)單的例子中,MovieLister 類(lèi)負責定位MovieFinder 的具體實(shí)現——它直接實(shí)例化后者的一個(gè)子類(lèi)。這樣一來(lái),MovieFinder 也就不成其為一個(gè)插件了,因為它并不是在運行期插入應用程序中的。而這些輕量級容器則使用了更為靈活的辦法,只要插件遵循一定的規則,一個(gè)獨立的組裝模塊就能夠將插件的具體實(shí)現“注射”到應用程序中。

因此,我想我們需要給這個(gè)模式起一個(gè)更能說(shuō)明其特點(diǎn)的名字——“控制反轉”這個(gè)名字太泛了,常常讓人有些迷惑。與多位IoC 愛(ài)好者討論之后,我們決定將這個(gè)模式叫做“依賴(lài)注入”(Dependency Injection)。

下面,我將開(kāi)始介紹Dependency Injection 模式的幾種不同形式。不過(guò),在此之前,我要首先指出:要消除應用程序對插件實(shí)現的依賴(lài),依賴(lài)注入并不是唯一的選擇,你也可以用Service Locator 模式獲得同樣的效果。介紹完Dependency Injection 模式之后,我也會(huì )談到Service Locator 模式。

編輯

依賴(lài)注入的幾種形式

Dependency Injection 模式的基本思想是:用一個(gè)單獨的對象(裝配器)來(lái)獲得MovieFinder的一個(gè)合適的實(shí)現,并將其實(shí)例賦給MovieLister 類(lèi)的一個(gè)字段。這樣一來(lái),我們就得到了圖2所示的依賴(lài)圖:

圖2:引入依賴(lài)注入器之后的依賴(lài)關(guān)系


依賴(lài)注入的形式主要有三種,我分別將它們叫做構造子注入(Constructor Injection)、設值方法注入(Setter Injection)和接口注入(Interface Injection)。如果讀過(guò)最近關(guān)于IoC 的一些討論材料,你不難看出:這三種注入形式分別就是type 1 IoC(接口注入)、type 2 IoC(設值方法注入)和type 3 IoC(構造子注入)。我發(fā)現數字編號往往比較難記,所以我使用了這里的命名方式。

使用PicoContainer 進(jìn)行構造子注入

首先,我要向讀者展示如何用一個(gè)名為PicoContainer 的輕量級容器完成依賴(lài)注入。之所以從這里開(kāi)始,主要是因為我在ThoughtWorks 公司的幾個(gè)同事在PicoContainer 的開(kāi)發(fā)社群中非?;钴S——沒(méi)錯,也可以說(shuō)是某種偏袒吧。

PicoContainer 通過(guò)構造子來(lái)判斷“如何將MovieFinder 實(shí)例注入MovieLister 類(lèi)”。因此,MovieLister 類(lèi)必須聲明一個(gè)構造子,并在其中包含所有需要注入的元素:

class MovieLister...public MovieLister(MovieFinder finder) {this.finder = finder;}

MovieFinder 實(shí)例本身也將由PicoContainer來(lái)管理,因此文本文件的名字也可以由容器注入:

class ColonMovieFinder...public ColonMovieFinder(String filename) {this.filename = filename;}

隨后,需要告訴PicoContainer:各個(gè)接口分別與哪個(gè)實(shí)現類(lèi)關(guān)聯(lián)、將哪個(gè)字符串注入MovieFinder組件。

private MutablePicoContainer configureContainer() {MutablePicoContainer pico = new DefaultPicoContainer();Parameter[] finderParams = {new ConstantParameter("movies1.txt")};pico.registerComponentImplementation(MovieFinder.class,ColonMovieFinder.class, finderParams);pico.registerComponentImplementation(MovieLister.class);return pico;}

這段配置代碼通常位于另一個(gè)類(lèi)。對于我們這個(gè)例子,使用我的MovieLister 類(lèi)的朋友需要在自己的設置類(lèi)中編寫(xiě)合適的配置代碼。當然,還可以將這些配置信息放在一個(gè)單獨的配置文件中,這也是一種常見(jiàn)的做法。你可以編寫(xiě)一個(gè)類(lèi)來(lái)讀取配置文件,然后對容器進(jìn)行合適的設置。盡管PicoContainer 本身并不包含這項功能,但另一個(gè)與它關(guān)系緊密的項目NanoContainer 提供了一些包裝,允許開(kāi)發(fā)者使用XML 配置文件保存配置信息。NanoContainer能夠解析XML 文件,并對底下的PicoContainer 進(jìn)行配置。這個(gè)項目的哲學(xué)觀(guān)念就是:將配置文件的格式與底下的配置機制分離開(kāi)。

使用這個(gè)容器,你寫(xiě)出的代碼大概會(huì )是這樣:

public void testWithPico() {MutablePicoContainer pico = configureContainer();MovieLister lister = (MovieLister)pico.getComponentInstance(MovieLister.class);Movie[] movies = lister.moviesDirectedBy("Sergio Leone");assertEquals("Once Upon a Time in the West",movies[0].getTitle());}

盡管在這里我使用了構造子注入,實(shí)際上PicoContainer 也支持設值方法注入,不過(guò)該項目的開(kāi)發(fā)者更推薦使用構造子注入。

使用Spring 進(jìn)行設值方法注入

Spring 框架是一個(gè)用途廣泛的企業(yè)級Java 開(kāi)發(fā)框架,其中包括了針對事務(wù)、持久化框架、web應用開(kāi)發(fā)和JDBC 等常用功能的抽象。和PicoContainer 一樣,它也同時(shí)支持構造子注入和設值方法注入,但該項目的開(kāi)發(fā)者更推薦使用設值方法注入——恰好適合這個(gè)例子。

為了讓MovieLister 類(lèi)接受注入, 我需要為它定義一個(gè)設值方法, 該方法接受類(lèi)型為MovieFinder的參數:

class MovieLister...private MovieFinder finder;public void setFinder(MovieFinder finder) {this.finder = finder;}

類(lèi)似地,在MovieFinder的實(shí)現類(lèi)中,我也定義了一個(gè)設值方法,接受類(lèi)型為String 的參數:

class ColonMovieFinder...public void setFilename(String filename) {this.filename = filename;}

第三步是設定配置文件。Spring 支持多種配置方式,你可以通過(guò)XML 文件進(jìn)行配置,也可以直接在代碼中配置。不過(guò),XML 文件是比較理想的配置方式。

<beans><bean id="MovieLister" class="spring.MovieLister"><property name="finder"><ref local="MovieFinder"/></property></bean><bean id="MovieFinder" class="spring.ColonMovieFinder"><property name="filename"><value>movies1.txt</value></property></bean></beans>

于是,測試代碼大概就像下面這樣:

public void testWithSpring() throws Exception {ApplicationContext ctx = newFileSystemXmlApplicationContext("spring.xml");MovieLister lister = (MovieLister) ctx.getBean("MovieLister");Movie[] movies = lister.moviesDirectedBy("Sergio Leone");assertEquals("Once Upon a Time in the West",movies[0].getTitle());}

接口注入

除了前面兩種注入技術(shù),還可以在接口中定義需要注入的信息,并通過(guò)接口完成注入。Avalon框架就使用了類(lèi)似的技術(shù)。在這里,我首先用簡(jiǎn)單的范例代碼說(shuō)明它的用法,后面還會(huì )有更深入的討論。

首先,我需要定義一個(gè)接口,組件的注入將通過(guò)這個(gè)接口進(jìn)行。在本例中,這個(gè)接口的用途是將一個(gè)MovieFinder實(shí)例注入繼承了該接口的對象。

public interface InjectFinder {void injectFinder(MovieFinder finder);}

這個(gè)接口應該由提供MovieFinder 接口的人一并提供。任何想要使用MovieFinder 實(shí)例的類(lèi)(例如MovieLister 類(lèi))都必須實(shí)現這個(gè)接口。

class MovieLister implements InjectFinder...public void injectFinder(MovieFinder finder) {this.finder = finder;}

然后,我使用類(lèi)似的方法將文件名注入MovieFinder的實(shí)現類(lèi):

public interface InjectFilename {void injectFilename (String filename);}class ColonMovieFinder implements MovieFinder, InjectFilename......public void injectFilename(String filename) {this.filename = filename;}

現在,還需要用一些配置代碼將所有的組件實(shí)現裝配起來(lái)。簡(jiǎn)單起見(jiàn),我直接在代碼中完成配置,并將配置好的MovieLister 對象保存在名為lister的字段中:

class IfaceTester...private MovieLister lister;private void configureLister() {ColonMovieFinder finder = new ColonMovieFinder();finder.injectFilename("movies1.txt");lister = new MovieLister();lister.injectFinder(finder);}

測試代碼則可以直接使用這個(gè)字段:

class IfaceTester...public void testIface() {configureLister();Movie[] movies = lister.moviesDirectedBy("Sergio Leone");assertEquals("Once Upon a Time in the West",movies[0].getTitle());}

編輯

使用Service Locator

依賴(lài)注入的最大好處在于:它消除了MovieLister類(lèi)對具體MovieFinder實(shí)現類(lèi)的依賴(lài)。這樣一來(lái), 我就可以把MovieLister 類(lèi)交給朋友, 讓他們根據自己的環(huán)境插入一個(gè)合適的MovieFinder實(shí)現即可。不過(guò),Dependency Injection 模式并不是打破這層依賴(lài)關(guān)系的唯一手段,另一種方法是使用Service Locator 模式。

Service Locator 模式背后的基本思想是:有一個(gè)對象(即服務(wù)定位器)知道如何獲得一個(gè)應用程序所需的所有服務(wù)。也就是說(shuō),在我們的例子中,服務(wù)定位器應該有一個(gè)方法,用于獲得一個(gè)MovieFinder 實(shí)例。當然,這不過(guò)是把麻煩換了一個(gè)樣子,我們仍然必須在MovieLister 中獲得服務(wù)定位器,最終得到的依賴(lài)關(guān)系如圖3 所示:

圖3:使用Service Locator 模式之后的依賴(lài)關(guān)系


在這里,我把ServiceLocator 類(lèi)實(shí)現為一個(gè)Singleton 的注冊表,于是MovieLister 就可以在實(shí)例化時(shí)通過(guò)ServiceLocator 獲得一個(gè)MovieFinder 實(shí)例。

class MovieLister...MovieFinder finder = ServiceLocator.movieFinder();class ServiceLocator...public static MovieFinder movieFinder() {return soleInstance.movieFinder;}private static ServiceLocator soleInstance;private MovieFinder movieFinder;

和注入的方式一樣,我們也必須對服務(wù)定位器加以配置。在這里,我直接在代碼中進(jìn)行配置,但設計一種通過(guò)配置文件獲得數據的機制也并非難事。

class Tester...private void configure() {ServiceLocator.load(new ServiceLocator(newColonMovieFinder("movies1.txt")));}class ServiceLocator...public static void load(ServiceLocator arg) {soleInstance = arg;}public ServiceLocator(MovieFinder movieFinder) {this.movieFinder = movieFinder;}

下面是測試代碼:

class Tester...public void testSimple() {configure();MovieLister lister = new MovieLister();Movie[] movies = lister.moviesDirectedBy("Sergio Leone");assertEquals("Once Upon a Time in the West",movies[0].getTitle());}

我時(shí)常聽(tīng)到這樣的論調:這樣的服務(wù)定位器不是什么好東西,因為你無(wú)法替換它返回的服務(wù)實(shí)現,從而導致無(wú)法對它們進(jìn)行測試。當然,如果你的設計很糟糕,你的確會(huì )遇到這樣的麻煩;但你也可以選擇良好的設計。在這個(gè)例子中,ServiceLocator 實(shí)例僅僅是一個(gè)簡(jiǎn)單的數據容器,只需要對它做一些簡(jiǎn)單的修改,就可以讓它返回用于測試的服務(wù)實(shí)現。

對于更復雜的情況,我可以從ServiceLocator 派生出多個(gè)子類(lèi),并將子類(lèi)型的實(shí)例傳遞給注冊表的類(lèi)變量。另外,我可以修改ServiceLocator 的靜態(tài)方法,使其調用ServiceLocator 實(shí)例的方法,而不是直接訪(fǎng)問(wèn)實(shí)例變量。我還可以使用特定于線(xiàn)程的存儲機制,從而提供特定于線(xiàn)程的服務(wù)定位器。所有這一切改進(jìn)都無(wú)須修改ServiceLocator 的使用者。

一種改進(jìn)的思路是:服務(wù)定位器仍然是一個(gè)注冊表,但不是Singleton。Singleton 的確是實(shí)現注冊表的一種簡(jiǎn)單途徑,但這只是一個(gè)實(shí)現時(shí)的決定,可以很輕松地改變它。

為定位器提供分離的接口

上面這種簡(jiǎn)單的實(shí)現方式有一個(gè)問(wèn)題:MovieLister 類(lèi)將依賴(lài)于整個(gè)ServiceLocator 類(lèi),但它需要使用的卻只是后者所提供的一項服務(wù)。我們可以針對這項服務(wù)提供一個(gè)單獨的接口,減少MovieLister 對ServiceLocator 的依賴(lài)程度。這樣一來(lái),MovieLister 就不必使用整個(gè)的ServiceLocator 接口,只需聲明它想要使用的那部分接口。

此時(shí),MovieLister 類(lèi)的提供者也應該一并提供一個(gè)定位器接口,使用者可以通過(guò)這個(gè)接口獲得MovieFinder實(shí)例。

public interface MovieFinderLocator {public MovieFinder movieFinder();

真實(shí)的服務(wù)定位器需要實(shí)現上述接口,提供訪(fǎng)問(wèn)MovieFinder 實(shí)例的能力:

MovieFinderLocator locator = ServiceLocator.locator();MovieFinder finder = locator.movieFinder();public static ServiceLocator locator() {return soleInstance;}public MovieFinder movieFinder() {return movieFinder;}private static ServiceLocator soleInstance;private MovieFinder movieFinder;

你應該已經(jīng)注意到了:由于想要使用接口,我們不能再通過(guò)靜態(tài)方法直接訪(fǎng)問(wèn)服務(wù)——我們必須首先通過(guò)ServiceLocator 類(lèi)獲得定位器實(shí)例,然后使用定位器實(shí)例得到我們想要的服務(wù)。

動(dòng)態(tài)服務(wù)定位器

上面是一個(gè)靜態(tài)定位器的例子——對于你所需要的每項服務(wù),ServiceLocator 類(lèi)都有對應的方法。這并不是實(shí)現服務(wù)定位器的唯一方式,你也可以創(chuàng )建一個(gè)動(dòng)態(tài)服務(wù)定位器,你可以在其中注冊需要的任何服務(wù),并在運行期決定獲得哪一項服務(wù)。

在本例中,ServiceLocator 使用一個(gè)map 來(lái)保存服務(wù)信息,而不再是將這些信息保存在字段中。此外,ServiceLocator 還提供了一個(gè)通用的方法,用于獲取和加載服務(wù)對象。

class ServiceLocator...private static ServiceLocator soleInstance;public static void load(ServiceLocator arg) {soleInstance = arg;}private Map services = new HashMap();public static Object getService(String key){return soleInstance.services.get(key);}public void loadService (String key, Object service) {services.put(key, service);}

同樣需要對服務(wù)定位器進(jìn)行配置,將服務(wù)對象與適當的關(guān)鍵字加載到定位器中:

class Tester...private void configure() {ServiceLocator locator = new ServiceLocator();locator.loadService("MovieFinder", newColonMovieFinder("movies1.txt"));ServiceLocator.load(locator);}

我使用與服務(wù)對象類(lèi)名稱(chēng)相同的字符串作為服務(wù)對象的關(guān)鍵字:

class MovieLister...MovieFinder finder = (MovieFinder)ServiceLocator.getService("MovieFinder");

總體而言,我不喜歡這種方式。無(wú)疑,這樣實(shí)現的服務(wù)定位器具有更強的靈活性,但它的使用方式不夠直觀(guān)明朗。我只有通過(guò)文本形式的關(guān)鍵字才能找到一個(gè)服務(wù)對象。相比之下,我更欣賞“通過(guò)一個(gè)方法明確獲得服務(wù)對象”的方式,因為這讓使用者能夠從接口定義中清楚地知道如何獲得某項服務(wù)。

用Avalon 兼顧服務(wù)定位器和依賴(lài)注入

Dependency Injection 和Service Locator 兩個(gè)模式并不是互斥的,你可以同時(shí)使用它們,Avalon 框架就是這樣的一個(gè)例子。Avalon 使用了服務(wù)定位器,但“如何獲得定位器”的信息則是通過(guò)注入的方式告知組件的。

對于前面一直使用的例子,Berin Loritsch 發(fā)送給了我一個(gè)簡(jiǎn)單的Avalon 實(shí)現版本:

public class MyMovieLister implements MovieLister, Serviceable {private MovieFinder finder;public void service( ServiceManager manager )throws ServiceException{finder = (MovieFinder)manager.lookup("finder");}

service 方法就是接口注入的例子, 它使容器可以將一個(gè)ServiceManager 對象注入MyMovieLister 對象。ServiceManager則是一個(gè)服務(wù)定位器。在這個(gè)例子中,MyMovieLister并不把ServiceManager 對象保存在字段中,而是馬上借助它找到MovieFinder 實(shí)例,并將后者保存起來(lái)。

編輯

作出一個(gè)選擇

到現在為止,我一直在闡述自己對這兩個(gè)模式( Dependency Injection 模式和Service Locator 模式)以及它們的變化形式的看法?,F在,我要開(kāi)始討論他們的優(yōu)點(diǎn)和缺點(diǎn),以便指出它們各自適用的場(chǎng)景。

Service Locator vs. Dependency Injection

首先,我們面臨Service Locator 和Dependency Injection 之間的選擇。應該注意,盡管我們前面那個(gè)簡(jiǎn)單的例子不足以表現出來(lái),實(shí)際上這兩個(gè)模式都提供了基本的解耦合能力——無(wú)論使用哪個(gè)模式,應用程序代碼都不依賴(lài)于服務(wù)接口的具體實(shí)現。兩者之間最重要的區別在于:這個(gè)“具體實(shí)現”以什么方式提供給應用程序代碼。使用Service Locator 模式時(shí),應用程序代碼直接向服務(wù)定位器發(fā)送一個(gè)消息,明確要求服務(wù)的實(shí)現;使用Dependency Injection 模式時(shí),應用程序代碼不發(fā)出顯式的請求,服務(wù)的實(shí)現自然會(huì )出現在應用程序代碼中,這也就是所謂“控制反轉”。

控制反轉是框架的共同特征,但它也要求你付出一定的代價(jià):它會(huì )增加理解的難度,并且給調試帶來(lái)一定的困難。所以,整體來(lái)說(shuō),除非必要,否則我會(huì )盡量避免使用它。這并不意味著(zhù)控制反轉不好,只是我認為在很多時(shí)候使用一個(gè)更為直觀(guān)的方案(例如Service Locator 模式)會(huì )比較合適。

一個(gè)關(guān)鍵的區別在于:使用Service Locator 模式時(shí),服務(wù)的使用者必須依賴(lài)于服務(wù)定位器。定位器可以隱藏使用者對服務(wù)具體實(shí)現的依賴(lài),但你必須首先看到定位器本身。所以,問(wèn)題的答案就很明朗了:選擇Service Locator 還是Dependency Injection,取決于“對定位器的依賴(lài)”是否會(huì )給你帶來(lái)麻煩。

Dependency Injection 模式可以幫助你看清組件之間的依賴(lài)關(guān)系:你只需觀(guān)察依賴(lài)注入的機制(例如構造子),就可以掌握整個(gè)依賴(lài)關(guān)系。而使用Service Locator 模式時(shí),你就必須在源代碼中到處搜索對服務(wù)定位器的調用。具備全文檢索能力的IDE 可以略微簡(jiǎn)化這一工作,但還是不如直接觀(guān)察構造子或者設值方法來(lái)得輕松。

這個(gè)選擇主要取決于服務(wù)使用者的性質(zhì)。如果你的應用程序中有很多不同的類(lèi)要使用一個(gè)服務(wù),那么應用程序代碼對服務(wù)定位器的依賴(lài)就不是什么大問(wèn)題。在前面的例子中, 我要把MovieLister 類(lèi)交給朋友去用,這種情況下使用服務(wù)定位器就很好:我的朋友們只需要對定位器做一點(diǎn)配置(通過(guò)配置文件或者某些配置性的代碼),使其提供合適的服務(wù)實(shí)現就可以了。在這種情況下,我看不出Dependency Injection 模式提供的控制反轉有什么吸引人的地方。

但是,如果把MovieLister 看作一個(gè)組件,要將它提供給別人寫(xiě)的應用程序去使用,情況就不同了。在這種時(shí)候,我無(wú)法預測使用者會(huì )使用什么樣的服務(wù)定位器API,每個(gè)使用者都可能有自己的服務(wù)定位器,而且彼此之間無(wú)法兼容。一種解決辦法是為每項服務(wù)提供單獨的接口,使用者可以編寫(xiě)一個(gè)適配器,讓我的接口與他們的服務(wù)定位器相配合。但即便如此,我仍然需要到第一個(gè)服務(wù)定位器中尋找我規定的接口。而且一旦用上了適配器,服務(wù)定位器所提供的簡(jiǎn)單性就被大大削弱了。

另一方面,如果使用Dependency Injection 模式,組件與注入器之間不會(huì )有依賴(lài)關(guān)系,因此組件無(wú)法從注入器那里獲得更多的服務(wù), 只能獲得配置信息中所提供的那些。這也是Dependency Injection 模式的局限性之一。

人們傾向于使用Dependency Injection 模式的一個(gè)常見(jiàn)理由是:它簡(jiǎn)化了測試工作。這里的關(guān)鍵是:出于測試的需要,你必須能夠輕松地在“真實(shí)的服務(wù)實(shí)現”與“供測試用的‘偽’組件”之間切換。但是,如果單從這個(gè)角度來(lái)考慮,Dependency Injection 模式和Service Locator模式其實(shí)并沒(méi)有太大區別:兩者都能夠很好地支持“偽”組件的插入。之所以很多人有“Dependency Injection 模式更利于測試”的印象,我猜是因為他們并沒(méi)有努力保證服務(wù)定位器的可替換性。這正是持續測試起作用的地方:如果你不能輕松地用一些“偽”組件將一個(gè)服務(wù)架起來(lái)以便測試,這就意味著(zhù)你的設計出現了嚴重的問(wèn)題。

當然,如果組件環(huán)境具有非常強的侵略性(就像EJB 框架那樣),測試的問(wèn)題會(huì )更加嚴重。我的觀(guān)點(diǎn)是:應該盡量減少這類(lèi)框架對應用程序代碼的影響,特別是不要做任何可能使“編輯-執行”的循環(huán)變慢的事情。用插件(plugin)機制取代重量級組件會(huì )對測試過(guò)程有很大幫助,這正是測試驅動(dòng)開(kāi)發(fā)(Test Driven Development,TDD)之類(lèi)實(shí)踐的關(guān)鍵所在。

所以,主要的問(wèn)題在于:代碼的作者是否希望自己編寫(xiě)的組件能夠脫離自己的控制、被使用在另一個(gè)應用程序中。如果答案是肯定的,那么他就不能對服務(wù)定位器做任何假設——哪怕最小的假設也會(huì )給使用者帶來(lái)麻煩。

構造子注入 vs. 設值方法注入

在組合服務(wù)時(shí),你總得遵循一定的約定,才可能將所有東西拼裝起來(lái)。依賴(lài)注入的優(yōu)點(diǎn)主要在于:它只需要非常簡(jiǎn)單的約定——至少對于構造子注入和設值方法注入來(lái)說(shuō)是這樣。相比于這兩者,接口注入的侵略性要強得多,比起Service Locator 模式的優(yōu)勢也不那么明顯。

所以,如果你想要提供一個(gè)組件給多個(gè)使用者,構造子注入和設值方法注入看起來(lái)很有吸引力:你不必在組件中加入什么希奇古怪的東西,注入器可以相當輕松地把所有東西配置起來(lái)。

設值函數注入和構造子注入之間的選擇相當有趣,因為它折射出面向對象編程的一些更普遍的問(wèn)題:應該在哪里填充對象的字段,構造子還是設值方法?

一直以來(lái),我首選的做法是盡量在構造階段就創(chuàng )建完整、合法的對象——也就是說(shuō),在構造子中填充對象字段。這樣做的好處可以追溯到Kent Beck 在Smalltalk Best Practice Patterns一書(shū)中介紹的兩個(gè)模式:Constructor Method 和Constructor Parameter Method。帶有參數的構造子可以明確地告訴你如何創(chuàng )建一個(gè)合法的對象。如果創(chuàng )建合法對象的方式不止一種,你還可以提供多個(gè)構造子,以說(shuō)明不同的組合方式。

構造子初始化的另一個(gè)好處是:你可以隱藏任何不可變的字段——只要不為它提供設值方法就行了。我認為這很重要:如果某個(gè)字段是不應該被改變的,“沒(méi)有針對該字段的設值方法”就很清楚地說(shuō)明了這一點(diǎn)。如果你通過(guò)設值方法完成初始化,暴露出來(lái)的設值方法很可能成為你心頭永遠的痛。(實(shí)際上,在這種時(shí)候我更愿意回避通常的設值方法約定,而是使用諸如initFoo 之類(lèi)的方法名,以表明該方法只應該在對象創(chuàng )建之初調用。)

不過(guò),世事總有例外。如果參數太多,構造子會(huì )顯得凌亂不堪,特別是對于不支持關(guān)鍵字參數的語(yǔ)言更是如此。的確,如果構造子參數列表太長(cháng),通常標志著(zhù)對象太過(guò)繁忙,理應將其拆分成幾個(gè)對象,但有些時(shí)候也確實(shí)需要那么多的參數。

如果有不止一種的方式可以構造一個(gè)合法的對象,也很難通過(guò)構造子描述這一信息,因為構造子之間只能通過(guò)參數的個(gè)數和類(lèi)型加以區分。這就是Factory Method 模式適用的場(chǎng)合了,工廠(chǎng)方法可以借助多個(gè)私有構造子和設值方法的組合來(lái)完成自己的任務(wù)。經(jīng)典Factory Method 模式的問(wèn)題在于:它們往往以靜態(tài)方法的形式出現,你無(wú)法在接口中聲明它們。你可以創(chuàng )建一個(gè)工廠(chǎng)類(lèi),但那又變成另一個(gè)服務(wù)實(shí)體了。“工廠(chǎng)服務(wù)”是一種不錯的技巧,但你仍然需要以某種方式實(shí)例化這個(gè)工廠(chǎng)對象,問(wèn)題仍然沒(méi)有解決。

如果要傳入的參數是像字符串這樣的簡(jiǎn)單類(lèi)型,構造子注入也會(huì )帶來(lái)一些麻煩。使用設值方法注入時(shí),你可以在每個(gè)設值方法的名字中說(shuō)明參數的用途;而使用構造子注入時(shí),你只能靠參數的位置來(lái)決定每個(gè)參數的作用,而記住參數的正確位置顯然要困難得多。

如果對象有多個(gè)構造子,對象之間又存在繼承關(guān)系,事情就會(huì )變得特別討厭。為了讓所有東西都正確地初始化,你必須將對子類(lèi)構造子的調用轉發(fā)給超類(lèi)的構造子,然后處理自己的參數。這可能造成構造子規模的進(jìn)一步膨脹。

盡管有這些缺陷,但我仍然建議你首先考慮構造子注入。不過(guò),一旦前面提到的問(wèn)題真的成了問(wèn)題,你就應該準備轉為使用設值方法注入。

在將Dependecy Injection 模式作為框架的核心部分的幾支團隊之間,“構造子注入還是設值方法注入”引發(fā)了很多的爭論。不過(guò),現在看來(lái),開(kāi)發(fā)這些框架的大多數人都已經(jīng)意識到:不管更喜歡哪種注入機制,同時(shí)為兩者提供支持都是有必要的。

代碼配置 vs. 配置文件

另一個(gè)問(wèn)題相對獨立,但也經(jīng)常與其他問(wèn)題牽涉在一起:如何配置服務(wù)的組裝,通過(guò)配置文件還是直接編碼組裝?對于大多數需要在多處部署的應用程序來(lái)說(shuō),一個(gè)單獨的配置文件會(huì )更合適。

配置文件幾乎都是XML 文件,XML 也的確很適合這一用途。不過(guò),有些時(shí)候直接在程序代碼中實(shí)現裝配會(huì )更簡(jiǎn)單。譬如一個(gè)簡(jiǎn)單的應用程序,也沒(méi)有很多部署上的變化,這時(shí)用幾句代碼來(lái)配置就比XML 文件要清晰得多。

與之相對的,有時(shí)應用程序的組裝非常復雜,涉及大量的條件步驟。一旦編程語(yǔ)言中的配置邏輯開(kāi)始變得復雜,你就應該用一種合適的語(yǔ)言來(lái)描述配置信息,使程序邏輯變得更清晰。然后,你可以編寫(xiě)一個(gè)構造器(builder)類(lèi)來(lái)完成裝配工作。如果使用構造器的情景不止一種,你可以提供多個(gè)構造器類(lèi),然后通過(guò)一個(gè)簡(jiǎn)單的配置文件在它們之間選擇。

我常常發(fā)現,人們太急于定義配置文件。編程語(yǔ)言通常會(huì )提供簡(jiǎn)捷而強大的配置管理機制,現代編程語(yǔ)言也可以將程序編譯成小的模塊,并將其插入大型系統中。如果編譯過(guò)程會(huì )很費力,腳本語(yǔ)言也可以在這方面提供幫助。

通常認為,配置文件不應該用編程語(yǔ)言來(lái)編寫(xiě),因為它們需要能夠被不懂編程的系統管理人員編輯。但是,這種情況出現的幾率有多大呢?我們真的希望不懂編程的系統管理人員來(lái)改變一個(gè)復雜的服務(wù)器端應用程序的事務(wù)隔離等級嗎?只有在非常簡(jiǎn)單的時(shí)候,非編程語(yǔ)言的配置文件才有最好的效果。如果配置信息開(kāi)始變得復雜,就應該考慮選擇一種合適的編程語(yǔ)言來(lái)編寫(xiě)配置文件。

在Java 世界里,我們聽(tīng)到了來(lái)自配置文件的不和諧音——每個(gè)組件都有它自己的配置文件,而且格式還各各不同。如果你要使用一打這樣的組件,你就得維護一打的配置文件,那會(huì )很快讓你煩死。

在這里,我的建議是:始終提供一種標準的配置方式,使程序員能夠通過(guò)同一個(gè)編程接口輕松地完成配置工作。至于其他的配置文件,僅僅把它們當作一種可選的功能。借助這個(gè)編程接口,開(kāi)發(fā)者可以輕松地管理配置文件。如果你編寫(xiě)了一個(gè)組件,則可以由組件的使用者來(lái)選擇如何管理配置信息:使用你的編程接口、直接操作配置文件格式,或者定義他們自己的配置文件格式,并將其與你的編程接口相結合。

分離配置與使用

所有這一切的關(guān)鍵在于:服務(wù)的配置應該與使用分開(kāi)。實(shí)際上,這是一個(gè)基本的設計原則——分離接口與實(shí)現。在面向對象程序里,我們在一個(gè)地方用條件邏輯來(lái)決定具體實(shí)例化哪一個(gè)類(lèi),以后的條件分支都由多態(tài)來(lái)實(shí)現,而不是繼續重復前面的條件邏輯,這就是“分離接口與實(shí)現”的原則。

如果對于一段代碼而言,接口與實(shí)現的分離還只是“有用”的話(huà),那么當你需要使用外部元素(例如組件和服務(wù))時(shí),它就是生死攸關(guān)的大事。這里的第一個(gè)問(wèn)題是:你是否希望將“選擇具體實(shí)現類(lèi)”的決策推遲到部署階段。如果是,那么你需要使用插入技術(shù)。使用了插入技術(shù)之后,插件的裝配原則上是與應用程序的其余部分分開(kāi)的,這樣你就可以輕松地針對不同的部署替換不同的配置。這種配置機制可以通過(guò)服務(wù)定位器來(lái)實(shí)現(Service Locator 模式),也可以借助依賴(lài)注入直接完成(Dependency Injection 模式)。

編輯

更多的問(wèn)題

在本文中,我關(guān)注的焦點(diǎn)是使用Dependency Injection 模式和Service Locator 模式進(jìn)行服務(wù)配置的基本問(wèn)題。還有一些與之相關(guān)的話(huà)題值得關(guān)注,但我已經(jīng)沒(méi)有時(shí)間繼續申發(fā)下去了。特別值得注意的是生命周期行為的問(wèn)題:某些組件具有特定的生命周期事件,例如“停止”、“開(kāi)始”等等。另一個(gè)值得注意的問(wèn)題是:越來(lái)越多的人對“如何在這些容器中運用面向方面(aspectoriented)的思想”產(chǎn)生了興趣。盡管目前還沒(méi)有認真準備過(guò)這方面的材料,但我也很希望以后能在這個(gè)話(huà)題上寫(xiě)一些東西。

關(guān)于這些問(wèn)題,你在專(zhuān)注于輕量級容器的網(wǎng)站上可以找到很多資料。瀏覽PicoContainer(http://www.picocontainer.org)或者Spring(http://www.springframework.org)的網(wǎng)站,你可以找到大量相關(guān)的討論,并由此引申出更多的話(huà)題。

編輯

結論和思考

在時(shí)下流行的輕量級容器都使用了一個(gè)共同的模式來(lái)組裝應用程序所需的服務(wù),我把這個(gè)模式稱(chēng)為Dependency Injection,它可以有效地替代Service Locator 模式。在開(kāi)發(fā)應用程序時(shí),兩者不相上下,但我認為Service Locator 模式略有優(yōu)勢,因為它的行為方式更為直觀(guān)。但是,如果你開(kāi)發(fā)的組件要交給多個(gè)應用程序去使用,那么Dependency Injection 模式會(huì )是更好的選擇。

如果你決定使用Dependency Injection 模式,這里還有幾種不同的風(fēng)格可供選擇。我建議你首先考慮構造子注入;如果遇到了某些特定的問(wèn)題,再改用設值方法注入。如果你要選擇一個(gè)容器,在其之上進(jìn)行開(kāi)發(fā),我建議你選擇同時(shí)支持這兩種注入方式的容器。

Service Locator 模式和Dependency Injection 模式之間的選擇并是最重要的,更重要的是:應該將服務(wù)的配置和應用程序內部對服務(wù)的使用分離開(kāi)。

編輯

致謝

在此,我要向幫助我理解本文中所提到的問(wèn)題、并對本文提出寶貴意見(jiàn)的幾個(gè)人表示感謝,他們是Rod Johnson、Paul Hammant、Joe Walnes、Aslak Hellesoy、Jon Tirsen 和Bill Caputo。另外,Berin Loritsch 和Hamilton Verissimo de Oliveira 在A(yíng)valon 方面給了我非常有用的建議,一并向他們表示感謝。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
深度理解依賴(lài)注入
Common Service Locator library
關(guān)于依賴(lài)注入(DI)的思考與理解
大公司為什么禁止在 Spring Boot 項目中使用 @Autowired 注解?
業(yè)務(wù)層設計之二(ServiceLocator)
利用j2ee建站
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久