依賴注入和控制反轉(zhuǎn)初見(jiàn)

Inversion of Control Containers and the Dependency Injection pattern扎翻譯

原文地址:控制反轉(zhuǎn)和依賴注入
參考1:Object Builder Application Block
參考2:深入理解依賴注入

總結(jié)

依賴注入是一個(gè)模式,一個(gè)模糊的概念,而控制反轉(zhuǎn)是實(shí)現(xiàn)依賴注入的一種實(shí)現(xiàn)方法。
實(shí)現(xiàn)控制反轉(zhuǎn)有三種方式:

  1. 構(gòu)造器注入
  2. setter注入
  3. 接口注入
    除了控制反轉(zhuǎn)可以實(shí)現(xiàn)依賴注入外,還有服務(wù)定位器模式也可以解除一定的依賴性。

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

從這里開(kāi)始翻譯:

In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly useful function is implemented by a single method.

在這個(gè)例子中,我正在寫(xiě)一個(gè)提供由特定導(dǎo)演指導(dǎo)的電影列表組件,這個(gè)非常有用的功能是通過(guò)一個(gè)方法來(lái)實(shí)現(xiàn)的。

MovieLister.class

public 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ù)的實(shí)現(xiàn)非常的幼稚,它需要一個(gè)finder對(duì)象(我們很快就知道)返回它的每一場(chǎng)電影,然后只是遍歷這個(gè) list (allMovies),篩選出由特定導(dǎo)演指導(dǎo)的電影。我不會(huì)去修補(bǔ)這個(gè)天真的代碼,因?yàn)檫@是本文真正意義上的基礎(chǔ)。

這篇文章的真正要點(diǎn)就是這個(gè)finder對(duì)象,特別的說(shuō),我們?nèi)绾螌?code>list對(duì)象和特定的finder對(duì)象連接起來(lái)。有趣的原因是,我希望moviesDirectedBy方法可以和所有電影如何存儲(chǔ)的方法完全獨(dú)立,所以在所有情況下,方法都可以引用finder對(duì)象,并且finder對(duì)象也會(huì)知道如何返回findAll方法,我可以定義一個(gè)finder的接口來(lái)解決這個(gè)問(wèn)題

public interface MovieFinder {
    List findAll();
}

目前這些都很好的解耦,但是在某些時(shí)候,我需要想出一個(gè)具體的類來(lái)存放這些電影,在這種情況下,我把這些放到了我的lister構(gòu)造器中。

MovieLister.class

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

總代碼如下:

MovieLister.class類

public class MovieLister{

    private MovieFinder finder;
    
    public MovieLister(){
        // ColonDelimitedMovieFinder 冒號(hào)分割的文本 獲取movie列表
        finder = new ColonDelimitedMovieFinder("movies1.txt");
    }
    
    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()]);
  }
}

MovieFinder.class類

public interfave MovieFinder{
    List findAll();
}

實(shí)現(xiàn)類的名稱來(lái)源于我從冒號(hào)分割的文本中獲取我的列表,我會(huì)告訴你多余細(xì)節(jié),畢竟只是一些實(shí)現(xiàn)。

現(xiàn)在,如果只是我自己而使用這個(gè)類,一切都很好很正常。但是,當(dāng)我的朋友非常渴望拷貝一份我的代碼的時(shí)候會(huì)發(fā)生什么事情呢?如果他們還把電影列表存儲(chǔ)在movies1.txt中,那么一切都很美妙。如果他們的存放電影文件的命名不同,那么把文件名稱放到一個(gè)配置文件中,也很容易。但是,如果他們有一個(gè)完全不同的形式存儲(chǔ)電影列表:SQL數(shù)據(jù)庫(kù),XML文件,web服務(wù),或者其他格式的文件呢?這種情況下,我們需要一個(gè)不同的類來(lái)獲取這些數(shù)據(jù)。因?yàn)楝F(xiàn)在我已經(jīng)定義了一個(gè)MovieFinder接口,這不會(huì)改變我的moviesDirectedBy方法,但是我仍然需要其他的方法,來(lái)正確的實(shí)例化finder接口

圖1: 在列表類中使用簡(jiǎn)單創(chuàng)建的依賴關(guān)系
圖1: 在列表類中使用簡(jiǎn)單創(chuàng)建的依賴關(guān)系

圖1: 在列表類中使用簡(jiǎn)單創(chuàng)建的依賴關(guān)系

Figure 1 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?
In my book P of EAA, we described this situation as a Plugin. The implementation class for the finder isn't linked into the program at compile time, since I don't know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.
Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.
So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.

圖一顯示了這種狀況的依賴關(guān)系,MovieLister類同時(shí)依賴于MovieFinder接口和實(shí)現(xiàn)。我們更喜歡如果MovieLister類只依賴MovieFinder的接口,但是我們?cè)撛趺醋瞿兀?/p>

在我的一本書(shū)中 P of EAA,我們把這種情況稱為一個(gè)Pluginfinder的實(shí)現(xiàn)類在編譯時(shí)沒(méi)有鏈接到程序中,因?yàn)槲也恢牢业呐笥褌儗⑹褂檬裁础O喾矗蚁M业?code>lister能夠和任何MovieFinder實(shí)現(xiàn)類一起工作,并且MovieFinder實(shí)現(xiàn)類將會(huì)自動(dòng)的插入到程序中,而不是通過(guò)我來(lái)掌控。問(wèn)題是我應(yīng)該怎樣完成這些鏈接,以便我的lister類對(duì)實(shí)現(xiàn)類是不知情的,但是它依然可以和finder實(shí)例完成工作。

把這些擴(kuò)展成一個(gè)真正的系統(tǒng),或許我們需要很多這樣的服務(wù)和組件。在每一種情況下,我們可以通過(guò)一個(gè)接口(如果組件沒(méi)有設(shè)計(jì)接口的話,可以使用組件),來(lái)讓我們的組件相互關(guān)聯(lián)。但是如果我們希望通過(guò)不同的方式部署這個(gè)系統(tǒng),我們需要使用插件來(lái)處理這些服務(wù)的交互,以便我們可以在不同的部署中使用不同的實(shí)現(xiàn)。

所以核心問(wèn)題是我們?nèi)绾螌⑦@些插件組裝到應(yīng)用文件之中?這也是一些輕量級(jí)容器所面臨的主要問(wèn)題之一,通常他們都是通過(guò)控制反轉(zhuǎn)來(lái)實(shí)現(xiàn)的。

控制反轉(zhuǎn)

當(dāng)這些容器談到他們?nèi)绾斡杏茫麄冋f(shuō)是因?yàn)閷?shí)現(xiàn)了“控制反轉(zhuǎn)”時(shí)候,我感到詫異。控制反轉(zhuǎn)是架構(gòu)的一個(gè)典型特征,所以當(dāng)這些輕量級(jí)框架說(shuō)因使用了控制反轉(zhuǎn)而特殊,這就像說(shuō)我的車是特殊的因?yàn)樗熊囕啞?/p>

問(wèn)題是:“他們反轉(zhuǎn)的是哪些方面的控制?”,當(dāng)我第一次遇到控制反轉(zhuǎn)的時(shí)候,是一個(gè)主要由用戶控制的界面。早期的用戶界面是被應(yīng)用程序所控制,你會(huì)有一系列如“輸入名稱”,”輸入地址”的命令;你的程序?qū)Ⅱ?qū)動(dòng)提示并承載每一個(gè)響應(yīng)。使用圖形(甚至基于屏幕的)UI,UI框架將會(huì)包含這個(gè)主循環(huán),并且,你的程序?qū)?huì)提供事件處理程序來(lái)代替屏幕上的各個(gè)區(qū)域。這個(gè)程序的主要控制權(quán)被顛倒了,從你轉(zhuǎn)移到了框架。

因此,我認(rèn)為我們需要一個(gè)更具體的名稱來(lái)表示這種模式,控制反轉(zhuǎn)(Inversion of Control)是一個(gè)過(guò)于籠統(tǒng)的術(shù)語(yǔ),因此人們感到困惑。因此與很多IOC提倡者進(jìn)行了大量討論之后,我們決定使用Dependency Injection(DI 依賴注入)這個(gè)詞。

我將開(kāi)始討論各種形式的依賴注入,但是我想指出的是,使用插件實(shí)現(xiàn)來(lái)去除依賴并不是應(yīng)用程序類的唯一方法。你可以使用的另一種模式是服務(wù)定位器模式,在完成解釋依賴注入之后,我會(huì)討論這個(gè)問(wèn)題。

依賴注入的形式

依賴注入的基本思想是有一個(gè)分隔的對(duì)象,一個(gè)裝配器,這個(gè)裝配器有適當(dāng)?shù)?code>finder接口實(shí)現(xiàn)來(lái)填充lister類中的字段,從而得到圖二的依賴關(guān)系圖

依賴注入的依賴關(guān)系
依賴注入的依賴關(guān)系

圖二:依賴注入的依賴關(guān)系

有三種主要方式的依賴注入,我目前叫他們構(gòu)造函數(shù)注入,setter注入和接口注入。如果你在目前關(guān)于依賴注入的討論中閱讀過(guò),你會(huì)聽(tīng)到這些被稱之為type 1 IOC(接口注入),type 2 IOC(setter注入),type 3 IOC(構(gòu)造器注入)的內(nèi)容。我發(fā)現(xiàn)數(shù)字名字很難記住,這就是為啥我在這兒使用了我曾經(jīng)叫的名字。

帶有PicoContainer的構(gòu)造器注入

我從展示如何使用輕量級(jí)框架picocontainer開(kāi)始.我從這里開(kāi)始的根本原因是我的幾位曾在ThoughtWorks重要的同事,在PicoContainer 的開(kāi)發(fā)中非常的活躍(沒(méi)錯(cuò),這是一種企業(yè)裙帶關(guān)系)
PicoContainer使用構(gòu)造器來(lái)決定如何將finder實(shí)現(xiàn)類注入到MoveiLister類之中。為了這個(gè)工作,MovieLister類需要聲明一個(gè)構(gòu)造器,它包含了它需要注入的所有東西。
MovieLister.class

public class MoveiLister{

    private MovieFinder finder;
    
    public MoveiLister(MovieFinder finder){
        this.finder = finder;
    }
    
    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()]);
  }
}

finder 本身也會(huì)被picocontainer容器所管理,同理,文本文件的文件名也會(huì)被注入到這個(gè)容器中

ColonMovieFinder.class

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

接下來(lái)picocontainer容器需要被告知哪些實(shí)現(xiàn)類需要與接口相關(guān)聯(lián),以及哪些字符串需要被注入到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;
}

這些配置代碼通常設(shè)置在一個(gè)不同的類中。在我們的例子中,每一個(gè)使用我的lister類的朋友可能會(huì)在某些啟動(dòng)類中編寫(xiě)他們自己的配置代碼。當(dāng)然,在一個(gè)單獨(dú)的配置文件中編寫(xiě)配置信息是很常見(jiàn)的。你可以寫(xiě)一個(gè)類去讀取配置文件并恰當(dāng)?shù)脑O(shè)置容器。雖然PicoContainer本身不包含這個(gè)功能,但是有一個(gè)名為NanoContainer的項(xiàng)目與之緊密相連,它提供了相關(guān)的包裝,允許你使用xml配置文件。這樣的一個(gè)納米級(jí)容器,將會(huì)解析xml,并配置一個(gè)潛在的PicoContainer,該項(xiàng)目的理念是將配置文件和底層機(jī)制分離。

要使用這個(gè)容器你可以這樣寫(xiě):

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());
}

盡管在這個(gè)例子中,我使用了構(gòu)造器注入,但是PicoContainer同樣支持setter注入,盡管它的開(kāi)發(fā)者更喜歡構(gòu)造器注入。

setter注入和spring

spring框架是企業(yè)級(jí)Java開(kāi)發(fā)的廣泛使用的框架,它包含事務(wù)抽象層,持久層框架,web應(yīng)用程序開(kāi)發(fā)和JDBC,像PicoContainer框架一樣,它同樣支持構(gòu)造器注入和setter注入,但是它的開(kāi)發(fā)人員跟傾向于setter注入,這使得它成為這個(gè)例子的合適選擇。

為了讓MovieLister接受注入,我在該類中定義了set方法

MovieLister.class

public class MovieLister{
    private MovieFinder finder;

    public MoveiLister(MovieFinder finder){
        this.finder = finder;
    }
    
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
    
    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()]);
  }
}

同樣的,我也為filename定義了一個(gè)set方法

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

第三步是設(shè)置文件的配置,spring支持通過(guò)XML文件進(jìn)行配置,也支持代碼配置,但XML文件是常見(jiàn)方式

<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>

測(cè)試文件應(yīng)該看起來(lái)是這樣子的:

public void testWithSpring() throws Exception {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("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());
}

接口注入

第三種注入方式是接口注入,Avalon是在某些情況下使用這種方式的框架的例子,稍后我會(huì)進(jìn)一步討論,但是現(xiàn)在,我將使用一些簡(jiǎn)單的示例代碼。

服務(wù)器定位模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,559評(píng)論 25 708
  • 我一直在努力擺脫孤獨(dú),沒(méi)想到現(xiàn)在學(xué)會(huì)了享受孤單。 或許每個(gè)人都在別人的歡慶里掩藏著失敗的悲傷。 這是一次極其細(xì)膩的...
    齊家能閱讀 164評(píng)論 0 0
  • 升級(jí)系統(tǒng)之后發(fā)現(xiàn)原來(lái)的pod install 等方法不管用了,提示如下錯(cuò)誤: -bash: /usr/local/...
    慭慭流觴閱讀 3,345評(píng)論 0 0
  • 園子里有二三十個(gè)品種的進(jìn)口月季,到了這個(gè)季節(jié),陸陸續(xù)續(xù)綻放,每天有花開(kāi),每天都驚喜。
    秋風(fēng)素影閱讀 309評(píng)論 1 1