乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      控制反轉(zhuǎn)和依賴注入模式(轉(zhuǎn))

       boyjiangkt727t 2019-07-17

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

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

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

      組件和服務(wù)

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

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

      服務(wù)和組件有某種相似之處:它們都將被外部的應(yīng)用程序使用。在我看來,兩者之間最大的差異在于:組件是在本地使用的(例如JAR文件、程序集、DLL、或者源碼導(dǎo)入);而服務(wù)是要通過同步或異步的遠(yuǎn)程接口來遠(yuǎn)程使用的(例如web service、消息系統(tǒng)、RPC,或者socket)。

      在本文中,我將主要使用“服務(wù)”這個(gè)詞,但文中的大多數(shù)邏輯也同樣適用于本地組件。實(shí)際上,為了方便地訪問遠(yuǎn)程服務(wù),你往往需要某種本地組件框架。不過,“組件或者服務(wù)”這樣一個(gè)詞組實(shí)在太麻煩了,而且“服務(wù)”這個(gè)詞當(dāng)下也很流行,所以本文將用“服務(wù)”指代這兩者。

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

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

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

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

       

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

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

      1 public interface MovieFinder {
      2     List findAll();
      3 }

       

      現(xiàn)在,兩個(gè)對(duì)象之間沒有什么耦合關(guān)系。但是,當(dāng)我要實(shí)際尋找影片時(shí),就必須涉及到MovieFinder的某個(gè)具體子類。在這里,我把涉及具體子類的代碼放在MovieLister類的構(gòu)造函數(shù)中。
      1 class MovieLister...
      2   private MovieFinder finder;
      3   public MovieLister() {
      4     finder = new ColonDelimitedMovieFinder("movies1.txt");
      5   }
      這個(gè)實(shí)現(xiàn)類的名字就說明:我將要從一個(gè)逗號(hào)分隔的文本文件中獲得影片列表。你不必操心具體的實(shí)現(xiàn)細(xì)節(jié),只要設(shè)想這樣一個(gè)實(shí)現(xiàn)類就可以了。

      如果這個(gè)類只由我自己使用,一切都沒問題。但是,如果我的朋友嘆服于這個(gè)精彩的功能,也想使用我的程序,那又會(huì)怎么樣呢?如果他們也把影片清單保存在一個(gè)逗號(hào)分隔的文本文件中,并且也把這個(gè)文件命名為“ movie1.txt ”,那么一切還是沒問題。如果他們只是給這個(gè)文件改改名,我也可以從一個(gè)配置文件獲得文件名,這也很容易。但是,如果他們用完全不同的方式——例如SQL 數(shù)據(jù)庫、XML 文件、web service,或者另一種格式的文本文件——來存儲(chǔ)影片清單呢?在這種情況下,我們需要用另一個(gè)類來獲取數(shù)據(jù)。由于已經(jīng)定義了MovieFinder接口,我可以不用修改moviesDirectedBy方法。但是,我仍然需要通過某種途徑獲得合適的MovieFinder實(shí)現(xiàn)類的實(shí)例。

      Figure 1

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


      在 P of EAA(Patterns of Enterprise Application Architecture)一書中,我們把這種情況稱為插件(plugin):MovieFinder的實(shí)現(xiàn)類不是在編譯期連入程序之中的,因?yàn)槲也⒉恢牢业呐笥褧?huì)使用哪個(gè)實(shí)現(xiàn)類。我們希望MovieLister類能夠與MovieFinder的任何實(shí)現(xiàn)類配合工作,并且允許在運(yùn)行期插入具體的實(shí)現(xiàn)類,插入動(dòng)作完全脫離我的控制。這里的問題就是:如何設(shè)計(jì)這個(gè)連接過程,使MovieLister類在不知道實(shí)現(xiàn)類細(xì)節(jié)的前提下與其實(shí)例協(xié)同工作。

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

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

      控制反轉(zhuǎn)

      幾位輕量級(jí)容器的作者曾驕傲地對(duì)我說:這些容器非常有用,因?yàn)樗鼈儗?shí)現(xiàn)了控制反轉(zhuǎn)。這樣的說辭讓我深感迷惑:控制反轉(zhuǎn)是框架所共有的特征,如果僅僅因?yàn)槭褂昧丝刂品崔D(zhuǎn)就認(rèn)為這些輕量級(jí)容器與眾不同,就好象在說我的轎車是與眾不同的,因?yàn)樗兴膫€(gè)輪子。

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


      對(duì)于這些新生的容器,它們反轉(zhuǎn)的是如何定位插件的具體實(shí)現(xiàn)。在前面那個(gè)簡(jiǎn)單的例子中,MovieLister類負(fù)責(zé)定位MovieFinder的具體實(shí)現(xiàn)——它直接實(shí)例化后者的一個(gè)子類。這樣一來,MovieFinder也就不成其為一個(gè)插件了,因?yàn)樗⒉皇窃谶\(yùn)行期插入應(yīng)用程序中的。而這些輕量級(jí)容器則使用了更為靈活的辦法,只要插件遵循一定的規(guī)則,一個(gè)獨(dú)立的組裝模塊就能夠?qū)⒉寮木唧w實(shí)現(xiàn)注射到應(yīng)用程序中。

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

      下面,我將開始介紹Dependency Injection模式的幾種不同形式。不過,在此之前,我要首先指出:要消除應(yīng)用程序?qū)Σ寮?shí)現(xiàn)的依賴,依賴注入并不是唯一的選擇,你也可以用Service Locator模式獲得同樣的效果。介紹完Dependency Injection模式之后,我也會(huì)談到Service Locator 模式。 

      依賴注入的幾種形式

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

      Figure 2

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


      使用PicoContainer 進(jìn)行構(gòu)造函數(shù)注入

      首先,我要向讀者展示如何用一個(gè)名為PicoContainer的輕量級(jí)容器完成依賴注入。之所以從這里開始,主要是因?yàn)槲以赥houghtWorks公司的幾個(gè)同事在PicoContainer的開發(fā)社群中非常活躍——沒錯(cuò),也可以說是某種偏袒吧。

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

      1 class MovieLister...
      2     public MovieLister(MovieFinder finder) {
      3         this.finder = finder;      
      4     }

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

      1 class ColonMovieFinder...
      2     public ColonMovieFinder(String filename) {
      3         this.filename = filename;
      4     }

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

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

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

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

      1 public void testWithPico() {
      2         MutablePicoContainer pico = configureContainer();
      3         MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
      4         Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
      5         assertEquals("Once Upon a Time in the West", movies[0].getTitle());
      6     }
      盡管在這里我使用了構(gòu)造函數(shù)注入,實(shí)際上PicoContainer也支持設(shè)值方法注入,不過該項(xiàng)目的開發(fā)者更推薦使用構(gòu)造函數(shù)注入。

       


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

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

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

      1 class MovieLister...
      2     private MovieFinder finder;
      3   public void setFinder(MovieFinder finder) {
      4     this.finder = finder;
      5   }
      類似地,在MovieFinder的實(shí)現(xiàn)類中,我也定義了一個(gè)設(shè)值方法,接受類型為String 的參數(shù):
      1 class ColonMovieFinder...
      2     public void setFilename(String filename) {
      3         this.filename = filename;
      4     }
      第三步是設(shè)定配置文件。Spring 支持多種配置方式,你可以通過XML 文件進(jìn)行配置,也可以直接在代碼中配置。不過,XML 文件是比較理想的配置方式。
      01 <beans>
      02         <bean id="MovieLister" class="spring.MovieLister">
      03             <property name="finder">
      04                 <ref local="MovieFinder"/>
      05             </property>
      06         </bean>
      07         <bean id="MovieFinder" class="spring.ColonMovieFinder">
      08             <property name="filename">
      09                 <value>movies1.txt</value>
      10             </property>
      11         </bean>
      12     </beans>
      于是,測(cè)試代碼大概就像下面這樣:
      1 public void testWithSpring() throws Exception {
      2     ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
      3     MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
      4     Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
      5     assertEquals("Once Upon a Time in the West", movies[0].getTitle());
      6 }

      接口注入

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

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

      1 public interface InjectFinder {
      2     void injectFinder(MovieFinder finder);
      3 }
      這個(gè)接口應(yīng)該由提供MovieFinder接口的人一并提供。任何想要使用MovieFinder實(shí)例的類(例如MovieLister類)都必須實(shí)現(xiàn)這個(gè)接口。
      1 class MovieLister implements InjectFinder...
      2     public void injectFinder(MovieFinder finder) {
      3         this.finder = finder;
      4     }
      然后,我使用類似的方法將文件名注入MovieFinder的實(shí)現(xiàn)類:
      1 public interface InjectFinderFilename {
      2     void injectFilename (String filename);
      3 }
      1 class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
      2     public void injectFilename(String filename) {
      3         this.filename = filename;
      4     }
      然后,還需要用一些配置代碼將所有的組件實(shí)現(xiàn)裝配起來。簡(jiǎn)單起見,我直接在代碼中完成配置,并將配置好的MovieLister 對(duì)象保存在名為lister的字段中:
      1 class Tester...
      2     private Container container;
      3  
      4      private void configureContainer() {
      5        container = new Container();
      6        registerComponents();
      7        registerInjectors();
      8        container.start();
      9     }
      配置的工作分兩步,通過查找鍵值注冊(cè)組件,同其他示例非常類似。
      1 class Tester...
      2   private void registerComponents() {
      3     container.registerComponent("MovieLister", MovieLister.class);
      4     container.registerComponent("MovieFinder", ColonMovieFinder.class);
      5   }

      新的一個(gè)注冊(cè)注入器的步驟是注冊(cè)依賴的組件。每一個(gè)注入接口需要一些用來注入依賴對(duì)象的代碼。這里我通過使用容器的注冊(cè)注入器對(duì)象來實(shí)現(xiàn)。每一個(gè)注入器對(duì)象實(shí)現(xiàn)了注入器接口。

      class Tester...
        private void registerInjectors() {
          container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));
          container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
        }
      
      public interface Injector {
        public void inject(Object target);
      
      }
      當(dāng)依賴是一個(gè)為這個(gè)容易編寫的類時(shí),為這個(gè)組件實(shí)現(xiàn)注入器接口本身是有意義的,就像我在movie finder中所做的那樣。對(duì)于一般的類,例如字符串,我在一段配置代碼中使用了內(nèi)部類。
      class ColonMovieFinder implements Injector......
        public void inject(Object target) {
          ((InjectFinder) target).injectFinder(this);        
        }
      
      class Tester...
        public static class FinderFilenameInjector implements Injector {
          public void inject(Object target) {
            ((InjectFinderFilename)target).injectFilename("movies1.txt");      
          }
          }
      讓后再測(cè)試中使用這個(gè)容器。
      class IfaceTester...
          public void testIface() {
            configureContainer();
            MovieLister lister = (MovieLister)container.lookup("MovieLister");
            Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
            assertEquals("Once Upon a Time in the West", movies[0].getTitle());
          }
      容器使用了聲明的注入器接口來計(jì)算出依賴,并且注入器注入了正確的依賴。(具體的容器實(shí)現(xiàn)在這里并不是什么重要的技術(shù),這里我不做展示。)

      使用Service Locator

      依賴注入的最大好處在于:它消除了MovieLister類對(duì)具體MovieFinder實(shí)現(xiàn)類的依賴。這樣一來,我就可以把MovieLister類交給朋友,讓他們根據(jù)自己的環(huán)境插入一個(gè)合適的MovieFinder實(shí)現(xiàn)即可。不過,Dependency Injection模式并不是打破這層依賴關(guān)系的唯一手段,另一種方法是使用Service Locator模式。 
      Service Locator模式背后的基本思想是:有一個(gè)對(duì)象(即服務(wù)定位器)知道如何獲得一個(gè)應(yīng)用程序所需的所有服務(wù)。也就是說,在我們的例子中,服務(wù)定位器應(yīng)該有一個(gè)方法,用于獲得一個(gè)MovieFinder實(shí)例。當(dāng)然,這不過是把麻煩換了一個(gè)樣子,我們?nèi)匀槐仨氃贛ovieLister中獲得服務(wù)定位器,最終得到的依賴關(guān)系如圖3 所示: 
      Figure 3

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


      在這里,我把ServiceLocator類實(shí)現(xiàn)為一個(gè)Singleton的注冊(cè)表,于是MovieLister就可以在實(shí)例化時(shí)通過ServiceLocator獲得一個(gè)MovieFinder實(shí)例。 
      1 class MovieLister...
      2     MovieFinder finder = ServiceLocator.movieFinder();
      1 class ServiceLocator...
      2     public static MovieFinder movieFinder() {
      3         return soleInstance.movieFinder;
      4     }
      5     private static ServiceLocator soleInstance;
      6     private MovieFinder movieFinder;
      和注入的方式一樣,我們也必須對(duì)服務(wù)定位器加以配置。在這里,我直接在代碼中進(jìn)行配置,但設(shè)計(jì)一種通過配置文件獲得數(shù)據(jù)的機(jī)制也并非難事。 
      1 class Tester...
      2     private void configure() {
      3         ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));
      4     }
      1 class ServiceLocator...
      2     public static void load(ServiceLocator arg) {
      3         soleInstance = arg;
      4     }
      5  
      6     public ServiceLocator(MovieFinder movieFinder) {
      7         this.movieFinder = movieFinder;
      8     }
      下面是測(cè)試代碼:
      1 class Tester...
      2     public void testSimple() {
      3         configure();
      4         MovieLister lister = new MovieLister();
      5         Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
      6         assertEquals("Once Upon a Time in the West", movies[0].getTitle());
      7     }

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

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

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

       


      為定位器提供分離的接口

      上面這種簡(jiǎn)單的實(shí)現(xiàn)方式有一個(gè)問題:MovieLister類將依賴于整個(gè)ServiceLocator類,但它需要使用的卻只是后者所提供的一項(xiàng)服務(wù)。我們可以針對(duì)這項(xiàng)服務(wù)提供一個(gè)單獨(dú)的接口,減少M(fèi)ovieLister對(duì)ServiceLocator的依賴程度。這樣一來,MovieLister就不必使用整個(gè)的ServiceLocator 接口,只需聲明它想要使用的那部分接口。 
      此時(shí),MovieLister 類的提供者也應(yīng)該一并提供一個(gè)定位器接口,使用者可以通過這個(gè)接口獲得MovieFinder實(shí)例。 
      public interface MovieFinderLocator {
          public MovieFinder movieFinder();
      真實(shí)的服務(wù)定位器需要實(shí)現(xià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;
      你應(yīng)該已經(jīng)注意到了:由于想要使用接口,我們不能再通過靜態(tài)方法直接訪問服務(wù)——我們必須首先通過ServiceLocator類獲得定位器實(shí)例,然后使用定位器實(shí)例得到我們想要的服務(wù)。 

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

      上面是一個(gè)靜態(tài)定位器的例子——對(duì)于你所需要的每項(xiàng)服務(wù),ServiceLocator類都有對(duì)應(yīng)的方法。這并不是實(shí)現(xiàn)服務(wù)定位器的唯一方式,你也可以創(chuàng)建一個(gè)動(dòng)態(tài)服務(wù)定位器,你可以在其中注冊(cè)需要的任何服務(wù),并在運(yùn)行期決定獲得哪一項(xiàng)服務(wù)。 
      在本例中,ServiceLocator使用一個(gè)map來保存服務(wù)信息,而不再是將這些信息保存在字段中。此外,ServiceLocator還提供了一個(gè)通用的方法,用于獲取和加載服務(wù)對(duì)象。 
      01 class ServiceLocator...
      02     private static ServiceLocator soleInstance;
      03     public static void load(ServiceLocator arg) {
      04         soleInstance = arg;
      05     }
      06     private Map services = new HashMap();
      07     public static Object getService(String key){
      08         return soleInstance.services.get(key);
      09     }
      10     public void loadService (String key, Object service) {
      11         services.put(key, service);
      12     }
      同樣需要對(duì)服務(wù)定位器進(jìn)行配置,將服務(wù)對(duì)象與適當(dāng)?shù)年P(guān)鍵字加載到定位器中: 
      1 class Tester...
      2     private void configure() {
      3         ServiceLocator locator = new ServiceLocator();
      4         locator.loadService("MovieFinder"new ColonMovieFinder("movies1.txt"));
      5         ServiceLocator.load(locator);
      6     }
      我使用與服務(wù)對(duì)象類名稱相同的字符串作為服務(wù)對(duì)象的關(guān)鍵字: 
      1 class MovieLister...
      2     MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");
      總體而言,我不喜歡這種方式。無疑,這樣實(shí)現(xiàn)的服務(wù)定位器具有更強(qiáng)的靈活性,但它的使用方式不夠直觀明朗。我只有通過文本形式的關(guān)鍵字才能找到一個(gè)服務(wù)對(duì)象。相比之下,我更欣賞通過一個(gè)方法明確獲得服務(wù)對(duì)象的方式,因?yàn)檫@讓使用者能夠從接口定義中清楚地知道如何獲得某項(xiàng)服務(wù)。 

      使用兼顧服務(wù)定位器和依賴注入

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

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

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

       

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

      決定使用哪一個(gè)選項(xiàng)

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

      服務(wù)定位器 vs 依賴注入

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

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


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

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

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


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

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

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

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

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


      構(gòu)造函數(shù)注入 vs. 設(shè)值方法注入

      在組合服務(wù)時(shí),你總得遵循一定的約定,才可能將所有東西拼裝起來。依賴注入的優(yōu)點(diǎn)主要在于:它只需要非常簡(jiǎn)單的約定——至少對(duì)于構(gòu)造函數(shù)注入和設(shè)值方法注入來說是這樣。

      相比于這兩者,接口注入的侵略性要強(qiáng)得多,比起Service Locator模式的優(yōu)勢(shì)也不那么明顯。所以,如果你想要提供一個(gè)組件給多個(gè)使用者,構(gòu)造函數(shù)注入和設(shè)值方法注入看起來很有吸引力。你不必在組件中加入什么希奇古怪的東西,注入器可以相當(dāng)輕松地把所有東西配置起來。


      設(shè)值函數(shù)注入和構(gòu)造函數(shù)注入之間的選擇相當(dāng)有趣,因?yàn)樗凵涑雒嫦驅(qū)ο缶幊痰囊恍└毡榈膯栴}:應(yīng)該在哪里填充對(duì)象的字段,構(gòu)造函數(shù)還是設(shè)值方法?

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

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

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

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


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

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

      盡管有這些缺陷,但我仍然建議你首先考慮構(gòu)造函數(shù)注入。不過,一旦前面提到的問題真的成了問題,你就應(yīng)該準(zhǔn)備轉(zhuǎn)為使用設(shè)值方法注入。 
      在將Dependecy Injection 模式作為框架的核心部分的幾支團(tuán)隊(duì)之間,構(gòu)造函數(shù)注入還是設(shè)值方法注入引發(fā)了很多的爭(zhēng)論。不過,現(xiàn)在看來,開發(fā)這些框架的大多數(shù)人都已經(jīng)意識(shí)到:不管更喜歡哪種注入機(jī)制,同時(shí)為兩者提供支持都是有必要的。 

      代碼配置or配置文件

      另一個(gè)問題相對(duì)獨(dú)立,但也經(jīng)常與其他問題牽涉在一起:如何配置服務(wù)的組裝,通過配置文件還是直接編碼組裝?對(duì)于大多數(shù)需要在多處部署的應(yīng)用程序來說,一個(gè)單獨(dú)的配置文件會(huì)更合適。配置文件幾乎都是XML 文件,XML 也的確很適合這一用途。不過,有些時(shí)候直接在程序代碼中實(shí)現(xiàn)裝配會(huì)更簡(jiǎn)單。譬如一個(gè)簡(jiǎn)單的應(yīng)用程序,也沒有很多部署上的變化,這時(shí)用幾句代碼來配置就比XML 文件要清晰得多。

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


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

      通常認(rèn)為,配置文件不應(yīng)該用編程語言來編寫,因?yàn)樗鼈冃枰軌虮徊欢幊痰南到y(tǒng)管理人員編輯。但是,這種情況出現(xiàn)的幾率有多大呢?我們真的希望不懂編程的系統(tǒng)管理人員來改變一個(gè)復(fù)雜的服務(wù)器端應(yīng)用程序的事務(wù)隔離等級(jí)嗎?只有在非常簡(jiǎn)單的時(shí)候,非編程語言的配置文件才有最好的效果。如果配置信息開始變得復(fù)雜,就應(yīng)該考慮選擇一種合適的編程語言來編寫配置文件。


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

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


      分離配置與使用

      所有這一切的關(guān)鍵在于:服務(wù)的配置應(yīng)該與使用分開。實(shí)際上,這是一個(gè)基本的設(shè)計(jì)原則——分離接口與實(shí)現(xiàn)。在面向?qū)ο蟪绦蚶?,我們?cè)谝粋€(gè)地方用條件邏輯來決定具體實(shí)例化哪一個(gè)類,以后的條件分支都由多態(tài)來實(shí)現(xiàn),而不是繼續(xù)重復(fù)前面的條件邏輯,這就是分離接口與實(shí)現(xiàn)的原則。

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

      更多的問題

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

      關(guān)于這些問題,你在專注于輕量級(jí)容器的網(wǎng)站上可以找到很多資料。瀏覽 picocontainer  或者   spring的網(wǎng)站,你可以找到大量相關(guān)的討論,并由此引申出更多的話題。

      結(jié)論和思考

      在時(shí)下流行的輕量級(jí)容器都使用了一個(gè)共同的模式來組裝應(yīng)用程序所需的服務(wù),我把這個(gè)模式稱為Dependency Injection,它可以有效地替代Service Locator模式。在開發(fā)應(yīng)用程序時(shí),兩者不相上下,但我認(rèn)為Service Locator模式略有優(yōu)勢(shì),因?yàn)樗男袨榉绞礁鼮橹庇^。但是,如果你開發(fā)的組件要交給多個(gè)應(yīng)用程序去使用,那么Dependency Injection模式會(huì)是更好的選擇。

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

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

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多