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

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

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

總結(jié)

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

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

一個簡單的例子

從這里開始翻譯:

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.

在這個例子中凰狞,我正在寫一個提供由特定導(dǎo)演指導(dǎo)的電影列表組件,這個非常有用的功能是通過一個方法來實現(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()]);
  }
  
}

這個函數(shù)的實現(xiàn)非常的幼稚端圈,它需要一個finder對象(我們很快就知道)返回它的每一場電影,然后只是遍歷這個 list (allMovies)子库,篩選出由特定導(dǎo)演指導(dǎo)的電影舱权。我不會去修補這個天真的代碼,因為這是本文真正意義上的基礎(chǔ)仑嗅。

這篇文章的真正要點就是這個finder對象宴倍,特別的說,我們?nèi)绾螌?code>list對象和特定的finder對象連接起來仓技。有趣的原因是鸵贬,我希望moviesDirectedBy方法可以和所有電影如何存儲的方法完全獨立,所以在所有情況下浑彰,方法都可以引用finder對象恭理,并且finder對象也會知道如何返回findAll方法,我可以定義一個finder的接口來解決這個問題

public interface MovieFinder {
    List findAll();
}

目前這些都很好的解耦郭变,但是在某些時候颜价,我需要想出一個具體的類來存放這些電影,在這種情況下诉濒,我把這些放到了我的lister構(gòu)造器中周伦。

MovieLister.class

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

總代碼如下:

MovieLister.class類

public class MovieLister{

    private MovieFinder finder;
    
    public MovieLister(){
        // ColonDelimitedMovieFinder 冒號分割的文本 獲取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();
}

實現(xiàn)類的名稱來源于我從冒號分割的文本中獲取我的列表,我會告訴你多余細節(jié)未荒,畢竟只是一些實現(xiàn)专挪。

現(xiàn)在,如果只是我自己而使用這個類片排,一切都很好很正常寨腔。但是,當我的朋友非陈使眩渴望拷貝一份我的代碼的時候會發(fā)生什么事情呢迫卢?如果他們還把電影列表存儲在movies1.txt中,那么一切都很美妙冶共。如果他們的存放電影文件的命名不同乾蛤,那么把文件名稱放到一個配置文件中每界,也很容易。但是家卖,如果他們有一個完全不同的形式存儲電影列表:SQL數(shù)據(jù)庫眨层,XML文件,web服務(wù)上荡,或者其他格式的文件呢趴樱?這種情況下,我們需要一個不同的類來獲取這些數(shù)據(jù)榛臼。因為現(xiàn)在我已經(jīng)定義了一個MovieFinder接口伊佃,這不會改變我的moviesDirectedBy方法,但是我仍然需要其他的方法沛善,來正確的實例化finder接口

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

圖1: 在列表類中使用簡單創(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類同時依賴于MovieFinder接口和實現(xiàn)。我們更喜歡如果MovieLister類只依賴MovieFinder的接口金刁,但是我們該怎么做呢帅涂?

在我的一本書中 P of EAA,我們把這種情況稱為一個Plugin尤蛮,finder的實現(xiàn)類在編譯時沒有鏈接到程序中媳友,因為我不知道我的朋友們將使用什么。相反产捞,我希望我的lister能夠和任何MovieFinder實現(xiàn)類一起工作醇锚,并且MovieFinder實現(xiàn)類將會自動的插入到程序中,而不是通過我來掌控坯临。問題是我應(yīng)該怎樣完成這些鏈接焊唬,以便我的lister類對實現(xiàn)類是不知情的,但是它依然可以和finder實例完成工作看靠。

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

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

控制反轉(zhuǎn)

當這些容器談到他們?nèi)绾斡杏蔑躅酰麄冋f是因為實現(xiàn)了“控制反轉(zhuǎn)”時候,我感到詫異缩挑〉剑控制反轉(zhuǎn)是架構(gòu)的一個典型特征,所以當這些輕量級框架說因使用了控制反轉(zhuǎn)而特殊供置,這就像說我的車是特殊的因為它有車輪谨湘。

問題是:“他們反轉(zhuǎn)的是哪些方面的控制?”芥丧,當我第一次遇到控制反轉(zhuǎn)的時候紧阔,是一個主要由用戶控制的界面。早期的用戶界面是被應(yīng)用程序所控制续担,你會有一系列如“輸入名稱”擅耽,”輸入地址”的命令;你的程序?qū)Ⅱ?qū)動提示并承載每一個響應(yīng)物遇。使用圖形(甚至基于屏幕的)UI乖仇,UI框架將會包含這個主循環(huán),并且询兴,你的程序?qū)峁┦录幚沓绦騺泶嫫聊簧系母鱾€區(qū)域乃沙。這個程序的主要控制權(quán)被顛倒了,從你轉(zhuǎn)移到了框架诗舰。

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

我將開始討論各種形式的依賴注入蝙茶,但是我想指出的是,使用插件實現(xiàn)來去除依賴并不是應(yīng)用程序類的唯一方法诸老。你可以使用的另一種模式是服務(wù)定位器模式隆夯,在完成解釋依賴注入之后,我會討論這個問題别伏。

依賴注入的形式

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

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

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

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

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

我從展示如何使用輕量級框架picocontainer開始.我從這里開始的根本原因是我的幾位曾在ThoughtWorks重要的同事领舰,在PicoContainer 的開發(fā)中非常的活躍(沒錯夫嗓,這是一種企業(yè)裙帶關(guān)系)
PicoContainer使用構(gòu)造器來決定如何將finder實現(xiàn)類注入到MoveiLister類之中。為了這個工作冲秽,MovieLister類需要聲明一個構(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 本身也會被picocontainer容器所管理锉桑,同理排霉,文本文件的文件名也會被注入到這個容器中

ColonMovieFinder.class

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

接下來picocontainer容器需要被告知哪些實現(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è)置在一個不同的類中刨仑。在我們的例子中郑诺,每一個使用我的lister類的朋友可能會在某些啟動類中編寫他們自己的配置代碼。當然杉武,在一個單獨的配置文件中編寫配置信息是很常見的辙诞。你可以寫一個類去讀取配置文件并恰當?shù)脑O(shè)置容器。雖然PicoContainer本身不包含這個功能轻抱,但是有一個名為NanoContainer的項目與之緊密相連飞涂,它提供了相關(guān)的包裝,允許你使用xml配置文件祈搜。這樣的一個納米級容器较店,將會解析xml,并配置一個潛在的PicoContainer容燕,該項目的理念是將配置文件和底層機制分離梁呈。

要使用這個容器你可以這樣寫:

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òu)造器注入蘸秘,但是PicoContainer同樣支持setter注入官卡,盡管它的開發(fā)者更喜歡構(gòu)造器注入。

setter注入和spring

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

為了讓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定義了一個set方法

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

第三步是設(shè)置文件的配置霞揉,spring支持通過XML文件進行配置旬薯,也支持代碼配置,但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>

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

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是在某些情況下使用這種方式的框架的例子,稍后我會進一步討論硕舆,但是現(xiàn)在秽荞,我將使用一些簡單的示例代碼。

服務(wù)器定位模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抚官,一起剝皮案震驚了整個濱河市扬跋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凌节,老刑警劉巖钦听,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異倍奢,居然都是意外死亡朴上,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門卒煞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痪宰,“玉大人,你說我怎么就攤上這事畔裕∫虑耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵扮饶,是天一觀的道長具练。 經(jīng)常有香客問我,道長甜无,這世上最難降的妖魔是什么扛点? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮毫蚓,結(jié)果婚禮上占键,老公的妹妹穿的比我還像新娘。我一直安慰自己元潘,他們只是感情好畔乙,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翩概,像睡著了一般牲距。 火紅的嫁衣襯著肌膚如雪返咱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天牍鞠,我揣著相機與錄音咖摹,去河邊找鬼。 笑死难述,一個胖子當著我的面吹牛萤晴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胁后,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼店读,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了攀芯?” 一聲冷哼從身側(cè)響起屯断,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侣诺,沒想到半個月后殖演,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡年鸳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年趴久,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阻星。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡朋鞍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妥箕,到底是詐尸還是另有隱情滥酥,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布畦幢,位于F島的核電站坎吻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宇葱。R本人自食惡果不足惜瘦真,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黍瞧。 院中可真熱鬧诸尽,春花似錦、人聲如沸印颤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至际看,卻和暖如春咸产,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仲闽。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工脑溢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赖欣。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓屑彻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畏鼓。 傳聞我的和親對象是個殘疾皇子酱酬,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)云矫,斷路器,智...
    卡卡羅2017閱讀 134,664評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,178評論 25 707
  • 我一直在努力擺脫孤獨汗菜,沒想到現(xiàn)在學會了享受孤單让禀。 或許每個人都在別人的歡慶里掩藏著失敗的悲傷。 這是一次極其細膩的...
    齊家能閱讀 161評論 0 0
  • 升級系統(tǒng)之后發(fā)現(xiàn)原來的pod install 等方法不管用了陨界,提示如下錯誤: -bash: /usr/local/...
    慭慭流觴閱讀 3,307評論 0 0
  • 園子里有二三十個品種的進口月季巡揍,到了這個季節(jié),陸陸續(xù)續(xù)綻放菌瘪,每天有花開腮敌,每天都驚喜。
    秋風素影閱讀 286評論 1 1