<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    DANCE WITH JAVA

    開發(fā)出高質(zhì)量的系統(tǒng)

    常用鏈接

    統(tǒng)計(jì)

    積分與排名

    好友之家

    最新評論

    依賴倒置、控制反轉(zhuǎn)和依賴注入辨析

    轉(zhuǎn)載自:http://www.contextfree.net/wangyw/source/dip_ioc.html

    依賴倒置、控制反轉(zhuǎn)和依賴注入辨析

     

      在《道法自然——面向?qū)ο髮?shí)踐指南》一書中,我們采用了一個(gè)對立統(tǒng)一的辯證關(guān)系來說明“模板方法”模式—— “正向依賴 vs. 依賴倒置”(參見:《道法自然》第15章[王詠武, 王詠剛 2004])。這種把“好萊塢”原則和 “依賴倒置”原則等量齊觀的看法其實(shí)來自于輕量級容器PicoContainer主頁上的一段話:
      “控制反轉(zhuǎn)(Inversion of Control)的一個(gè)著名的同義原則是由Robert C. Martin提出的依賴倒置原則(Dependency Inversion Principle),它的另一個(gè)昵稱是好萊塢原則(Hollywood Principle:不要調(diào)用我,讓我來調(diào)用你)”[PicoContainer 2004]。
      和網(wǎng)友們在CSDN Blog上進(jìn)行了深入的討論后,我又把這些概念重新梳理了一下。我發(fā)現(xiàn),這幾個(gè)概念雖然在思路和動(dòng)機(jī)等宏觀層面上是統(tǒng)一的,但在具體的應(yīng)用層面還是存在著許多很微妙的差別。本文通過幾個(gè)簡單的例子對依賴倒置(Dependency Inversion Principle)、控制反轉(zhuǎn)(Inversion of Control)、依賴注入(Dependency Injection)等概念進(jìn)行了更為深入的辨析,也算是對于《道法自然》正文內(nèi)容的一個(gè)補(bǔ)充吧。

    依賴和耦合(Dependency and Coupling) 

      Rational Rose的幫助文檔上是這樣定義“依賴”關(guān)系的:“依賴描述了兩個(gè)模型元素之間的關(guān)系,如果被依賴的模型元素發(fā)生變化就會(huì)影響到另一個(gè)模型元素。典型的,在類圖上,依賴關(guān)系表明客戶類的操作會(huì)調(diào)用服務(wù)器類的操作。”
      Martin Fowler在《Reducing Coupling》一文中這樣描述耦合:“如果改變程序的一個(gè)模塊要求另一個(gè)模塊同時(shí)發(fā)生變化,就認(rèn)為這兩個(gè)模塊發(fā)生了耦合。” [Fowler 2001]
      從上面的定義可以看出:如果模塊A調(diào)用模塊B提供的方法,或訪問模塊B中的某些數(shù)據(jù)成員(當(dāng)然,在面向?qū)ο箝_發(fā)中一般不提倡這樣做),我們就認(rèn)為模塊A依賴于模塊B,模塊A和模塊B之間發(fā)生了耦合。
      那么,依賴對于我們來說究竟是好事還是壞事呢?
      由于人類的理解力有限,大多數(shù)人難以理解和把握過于復(fù)雜的系統(tǒng)。把軟件系統(tǒng)劃分成多個(gè)模塊,可以有效控制模塊的復(fù)雜度,使每個(gè)模塊都易于理解和維護(hù)。但在這種情況下,模塊之間就必須以某種方式交換信息,也就是必然要發(fā)生某種耦合關(guān)系。如果某個(gè)模塊和其它模塊沒有任何關(guān)聯(lián)(哪怕只是潛在的或隱含的依賴關(guān)系),我們就幾乎可以斷定,該模塊不屬于此軟件系統(tǒng),應(yīng)該從系統(tǒng)中剔除。如果所有模塊之間都沒有任何耦合關(guān)系,其結(jié)果必然是:整個(gè)軟件不過是多個(gè)互不相干的系統(tǒng)的簡單堆積,對每個(gè)系統(tǒng)而言,所有功能還是要在一個(gè)模塊中實(shí)現(xiàn),這等于沒有做任何模塊的分解。
      因此,模塊之間必定會(huì)有這樣或那樣的依賴關(guān)系,永遠(yuǎn)不要幻想消除所有依賴。但是,過強(qiáng)的耦合關(guān)系(如一個(gè)模塊的變化會(huì)造成一個(gè)或多個(gè)其他模塊也同時(shí)發(fā)生變化的依賴關(guān)系)會(huì)對軟件系統(tǒng)的質(zhì)量造成很大的危害。特別是當(dāng)需求發(fā)生變化時(shí),代碼的維護(hù)成本將非常高。所以,我們必須想盡辦法來控制和消解不必要的耦合,特別是那種會(huì)導(dǎo)致其它模塊發(fā)生不可控變化的依賴關(guān)系。依賴倒置、控制反轉(zhuǎn)、依賴注入等原則就是人們在和依賴關(guān)系進(jìn)行艱苦卓絕的斗爭過程中不斷產(chǎn)生和發(fā)展起來的。

    接口和實(shí)現(xiàn)分離 

      把接口和實(shí)現(xiàn)分開是人們試圖控制依賴關(guān)系的第一個(gè)嘗試,圖 1是Robert C. Martin在《依賴倒置》[Martin 1996]一文中所舉的第一個(gè)例子。其中,ReadKeyboard()和WritePrinter()為函數(shù)庫中的兩個(gè)函數(shù),應(yīng)用程序循環(huán)調(diào)用這兩個(gè)函數(shù),以便把用戶鍵入的字符拷貝到打印機(jī)輸出。

      為了使應(yīng)用程序不依賴于函數(shù)庫的具體實(shí)現(xiàn),C語言把函數(shù)的定義寫在了一個(gè)分離的頭文件(函數(shù)庫.h)中。這種做法的好處是:雖然應(yīng)用程序要調(diào)用函數(shù)庫、依賴于函數(shù)庫,但是,當(dāng)我們要改變函數(shù)庫的實(shí)現(xiàn)時(shí),只要重寫函數(shù)的實(shí)現(xiàn)代碼,應(yīng)用程序無需發(fā)生變化。例如,改變函數(shù)庫.c文件,把WritePrinter()函數(shù)重新實(shí)現(xiàn)成向磁盤中輸出,這時(shí)只要將應(yīng)用程序和函數(shù)庫重新鏈接,程序的功能就會(huì)發(fā)生相應(yīng)的變化。
      上面的函數(shù)庫也可以采用C++語言來實(shí)現(xiàn)。我們通常把這種用面向?qū)ο蠹夹g(shù)實(shí)現(xiàn)的,為應(yīng)用程序提供多個(gè)支持類的模塊稱為 “類庫”,如圖 2所示。這種通過分離接口和實(shí)現(xiàn)來消解應(yīng)用程序和類庫之間依賴關(guān)系的做法具有以下特點(diǎn):
      1. 應(yīng)用程序調(diào)用類庫,依賴于類庫。
      2. 接口和實(shí)現(xiàn)的分離從一定的程度上消解了這個(gè)依賴關(guān)系,具體實(shí)現(xiàn)可以在編譯期間發(fā)生變化。但是,這種消解方法的作用非常有限。比如說,一個(gè)系統(tǒng)中無法容納多個(gè)實(shí)現(xiàn),不同的實(shí)現(xiàn)不能動(dòng)態(tài)發(fā)生變化,用WritePrinter函數(shù)名來實(shí)現(xiàn)向磁盤中輸出的功能也顯得非常古怪,等等。
      3. 類庫可以單獨(dú)重用。但是應(yīng)用程序不能脫離類庫而重用,除非提供一個(gè)實(shí)現(xiàn)了相同接口的類庫。

     

    依賴倒置(Dependency Inversion Principle) 

      可以看出,上面討論的簡單分離接口的方法對于依賴關(guān)系的消解作用非常有限。Java語言提供了純粹的接口類,這種接口類不包括任何實(shí)現(xiàn)代碼,可以更好地隔離兩個(gè)模塊。C++語言中雖然沒有定義這種純粹的接口類,但所有成員函數(shù)都是純虛函數(shù)的抽象類也不包含任何實(shí)現(xiàn)代碼,可以起到類似于Java接口類的作用。為了和上一節(jié)中提到的簡單接口相區(qū)別,本文后面將把基于Java 接口類或C++抽象類定義的接口稱為抽象接口。依賴倒置原則就是建立在抽象接口的基礎(chǔ)上的。Robert Martin這樣描述依賴倒置原則[Martin 1996]:
      A. 上層模塊不應(yīng)該依賴于下層模塊,它們共同依賴于一個(gè)抽象。
      B. 抽象不能依賴于具象,具象依賴于抽象。
      其含義是:為了消解兩個(gè)模塊間的依賴關(guān)系,應(yīng)該在兩個(gè)模塊之間定義一個(gè)抽象接口,上層模塊調(diào)用抽象接口定義的函數(shù),下層模塊實(shí)現(xiàn)該接口。如圖 3所示,對于上一節(jié)的例子,我們可以定義兩個(gè)抽象類Reader和Writer作為抽象接口,其中的Read()和Write()函數(shù)都是純虛函數(shù),而具體的KeyboardReader和PrinterWriter類實(shí)現(xiàn)了這些接口。當(dāng)應(yīng)用程序調(diào)用Read()和Write()函數(shù)時(shí),由于多態(tài)性機(jī)制的作用,實(shí)際調(diào)用的是具體的KeyboardReader和PrinterWriter類中的實(shí)現(xiàn)。因此,抽象接口隔離了應(yīng)用程序和類庫中的具體類,使它們之間沒有直接的耦合關(guān)系,可以獨(dú)立地?cái)U(kuò)展或重用。例如,我們可以用類似的方法實(shí)現(xiàn)FileReader或DiskWriter類,應(yīng)用程序既可以根據(jù)需要選擇從鍵盤或文件輸入,也可以選擇向打印機(jī)或磁盤輸出,甚至同時(shí)完成多種不同的輸入、輸出任務(wù)。由此可以總結(jié)出,這種通過抽象接口消解應(yīng)用程序和類庫之間依賴關(guān)系的做法具有以下特點(diǎn):
      1. 應(yīng)用程序調(diào)用類庫的抽象接口,依賴于類庫的抽象接口;具體的實(shí)現(xiàn)類派生自類庫的抽象接口,也依賴于類庫的抽象接口。
      2. 應(yīng)用程序和具體的類庫實(shí)現(xiàn)完全獨(dú)立,相互之間沒有直接的依賴關(guān)系,只要保持接口類的穩(wěn)定,應(yīng)用程序和類庫的具體實(shí)現(xiàn)都可以獨(dú)立地發(fā)生變化。
      3. 類庫完全可以獨(dú)立重用,應(yīng)用程序可以和任何一個(gè)實(shí)現(xiàn)了相同抽象接口的類庫協(xié)同工作。

      一般情況下,由于類庫的設(shè)計(jì)者并不知道應(yīng)用程序會(huì)如何使用類庫,抽象接口大多由類庫設(shè)計(jì)者根據(jù)自己設(shè)想的典型使用模式總結(jié)出來,并保留一定的靈活度,以提供給應(yīng)用程序的開發(fā)者使用。
      但還有另外一種情況。圖 4是Martin Fowler在《Reducing Coupling》一文中使用的一個(gè)例子[Fowler 2001]。其中,Domain包要使用數(shù)據(jù)庫包,即Domain包依賴于數(shù)據(jù)庫包。為了隔離Domain包和數(shù)據(jù)庫包,可以引入一個(gè)Mapper包。如果在特定的情況下,我們希望Domain包能夠被多次重用,而Mapper包可以隨時(shí)變化,那么,我們就必須防止Domain包過分地依賴于Mapper包。這時(shí),可以由 Domain包的設(shè)計(jì)者總結(jié)出自己需要的抽象接口(如Store),而由Mapper包的設(shè)計(jì)者來實(shí)現(xiàn)該抽象接口。這樣一來,無論是在接口層面,還是在實(shí)現(xiàn)層面,依賴關(guān)系都完全顛倒過來了。

     

    控制反轉(zhuǎn)(Inversion of Control) 

      前面描述的是應(yīng)用程序和類庫之間的依賴關(guān)系。如果我們開發(fā)的不是類庫,而是框架系統(tǒng),依賴關(guān)系就會(huì)更強(qiáng)烈一點(diǎn)。那么,該如何消解框架和應(yīng)用程序之間的依賴關(guān)系呢?
      《道法自然》第5章描述了框架和類庫之間的區(qū)別:
      “框架和類庫最重要的區(qū)別是:框架是一個(gè)‘半成品’的應(yīng)用程序,而類庫只包含一系列可被應(yīng)用程序調(diào)用的類。
      “類庫給用戶提供了一系列可復(fù)用的類,這些類的設(shè)計(jì)都符合面向?qū)ο笤瓌t和模式。用戶使用時(shí),可以創(chuàng)建這些類的實(shí)例,或從這些類中繼承出新的派生類,然后調(diào)用類中相應(yīng)的功能。在這一過程中,類庫總是被動(dòng)地響應(yīng)用戶的調(diào)用請求。
      “框架則會(huì)為某一特定目的實(shí)現(xiàn)一個(gè)基本的、可執(zhí)行的架構(gòu)。框架中已經(jīng)包含了應(yīng)用程序從啟動(dòng)到運(yùn)行的主要流程,流程中那些無法預(yù)先確定的步驟留給用戶來實(shí)現(xiàn)。程序運(yùn)行時(shí),框架系統(tǒng)自動(dòng)調(diào)用用戶實(shí)現(xiàn)的功能組件。這時(shí),框架系統(tǒng)的行為是主動(dòng)的。
      “我們可以說,類庫是死的,而框架是活的。應(yīng)用程序通過調(diào)用類庫來完成特定的功能,而框架則通過調(diào)用應(yīng)用程序來實(shí)現(xiàn)整個(gè)操作流程。框架是控制倒置原則的完美體現(xiàn)。”
      框架系統(tǒng)的一個(gè)最好的例子就是圖形用戶界面(GUI)系統(tǒng)。一個(gè)簡單的,使用面向過程的設(shè)計(jì)方法開發(fā)的GUI系統(tǒng)如圖 5所示。

      從圖 5中可以看出,應(yīng)用程序調(diào)用GUI框架中的CreateWindow()函數(shù)來創(chuàng)建窗口,在這里,我們可以說應(yīng)用程序依賴于GUI框架。但GUI框架并不了解該窗口接收到窗口消息后應(yīng)該如何處理,這一點(diǎn)只有應(yīng)用程序最為清楚。因此,當(dāng)GUI框架需要發(fā)送窗口消息時(shí),又必須調(diào)用應(yīng)用程序定義的某個(gè)特定的窗口函數(shù)(如上圖中的MyWindowProc)。這時(shí),GUI框架又必須依賴于應(yīng)用程序。這是一個(gè)典型的雙向依賴關(guān)系。這種雙向依賴關(guān)系有一個(gè)非常嚴(yán)重的缺陷:由于GUI框架調(diào)用了應(yīng)用程序中的某個(gè)特定函數(shù)(MyWindowProc), GUI框架根本無法獨(dú)立存在;換一個(gè)新的應(yīng)用程序,GUI框架多半就要做相應(yīng)的修改。因此,如何消解框架系統(tǒng)對應(yīng)用程序的依賴關(guān)系是實(shí)現(xiàn)框架系統(tǒng)的關(guān)鍵。
      并非只有面向?qū)ο蟮姆椒ú拍芙鉀Q這一問題。WIN32 API早就為我們提供了在面向過程的設(shè)計(jì)思路下解決類似問題的范例。類WIN32 的架構(gòu)模型如圖 6所示。
      在圖 6中,應(yīng)用程序調(diào)用CreateWindow()函數(shù)時(shí),要傳遞一個(gè)消息處理函數(shù)的指針給GUI框架(對WIN32而言,我們在注冊窗口類時(shí)傳遞這一指針),GUI框架把該指針記錄在窗口信息結(jié)構(gòu)中。需要發(fā)送窗口消息時(shí),GUI框架就通過該指針調(diào)用窗口函數(shù)。和圖 5 相比,GUI框架仍然需要調(diào)用應(yīng)用程序,但這一調(diào)用從一個(gè)硬編碼的函數(shù)調(diào)用變成了一個(gè)由應(yīng)用程序事先注冊被調(diào)用對象的動(dòng)態(tài)調(diào)用。圖 6用一條虛線表示這種動(dòng)態(tài)調(diào)用。可以看出,這種動(dòng)態(tài)的調(diào)用關(guān)系有一個(gè)非常大的好處:當(dāng)應(yīng)用程序發(fā)生變化時(shí),它可以自行改變框架系統(tǒng)的調(diào)用目標(biāo),GUI框架無需隨之發(fā)生變化。現(xiàn)在,我們可以說,雖然還存在著從GUI框架到應(yīng)用程序的調(diào)用關(guān)系,但GUI框架已經(jīng)完全不再依賴于應(yīng)用程序了。這種動(dòng)態(tài)調(diào)用機(jī)制通常也被稱為“回調(diào)函數(shù)”。
      在面向?qū)ο箢I(lǐng)域,“回調(diào)函數(shù)”的替代物就是“模板方法模式”,也就是“好萊塢原則(不要調(diào)用我們,讓我們調(diào)用你)”。GUI框架的一個(gè)面向?qū)ο蟮膶?shí)現(xiàn)如圖 7所示。
      圖 7中,“GUI框架抽象接口”是GUI框架系統(tǒng)提供給應(yīng)用程序使用的接口。抽象出該接口的動(dòng)機(jī)是根據(jù)“依賴倒置”的原則,消解從應(yīng)用程序到GUI框架之間的直接依賴關(guān)系,以使得GUI框架實(shí)現(xiàn)的變化對應(yīng)用程序的影響最小化。Window接口類則是“模板方法模式”的核心。應(yīng)用程序調(diào)用CreateWindow()函數(shù)時(shí),GUI框架會(huì)把該窗口的引用保存在窗口鏈表中。需要發(fā)送窗口消息時(shí),GUI框架就調(diào)用窗口對象的SendMessage()函數(shù),該函數(shù)是實(shí)現(xiàn)在Window類中的非虛成員函數(shù)。SendMessage()函數(shù)又調(diào)用WindowProc()虛函數(shù),這里實(shí)際執(zhí)行的是應(yīng)用程序MyWindow類中實(shí)現(xiàn)的WindowProc()函數(shù)。在圖 7中,我們已經(jīng)看不到從GUI框架到應(yīng)用程序之間的直接依賴關(guān)系了。因此,模板方法模式完全實(shí)現(xiàn)了回調(diào)函數(shù)的動(dòng)態(tài)調(diào)用機(jī)制,消解了從框架到應(yīng)用程序之間的依賴關(guān)系。
      從上面的分析可以看出,模板方法模式是框架系統(tǒng)的基礎(chǔ),任何框架系統(tǒng)都離不開模板方法模式。Martin Fowler也說 [Folwer 2004],“幾位輕量級容器的作者曾驕傲地對我說:這些容器非常有用,因?yàn)樗鼈儗?shí)現(xiàn)了‘控制反轉(zhuǎn)’。這樣的說辭讓我深感迷惑:控制反轉(zhuǎn)是框架所共有的特征,如果僅僅因?yàn)槭褂昧丝刂品崔D(zhuǎn)就認(rèn)為這些輕量級容器與眾不同,就好像在說‘我的轎車是與眾不同的,因?yàn)樗兴膫€(gè)輪子’。問題的關(guān)鍵在于:它們反轉(zhuǎn)了哪方面的控制?我第一次接觸到的控制反轉(zhuǎn)針對的是用戶界面的主控權(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)用程序移到了框架。”
      確實(shí):對比圖 3和圖 7可以看出,使用普通類庫時(shí),程序的主循環(huán)位于應(yīng)用程序中,而使用框架系統(tǒng)的應(yīng)用程序不再包括一個(gè)主循環(huán),只是實(shí)現(xiàn)某些框架定義的接口,框架系統(tǒng)負(fù)責(zé)實(shí)現(xiàn)系統(tǒng)運(yùn)行的主循環(huán),并在必要的時(shí)候通過模板方法模式調(diào)用應(yīng)用程序。
      也就是說,雖然“依賴倒置”和“控制反轉(zhuǎn)”在設(shè)計(jì)層面上都是消解模塊耦合的有效方法,也都是試圖令具體的、易變的模塊依賴于抽象的、穩(wěn)定的模塊的基本原則,但二者在使用語境和關(guān)注點(diǎn)上存在差異:“依賴倒置”強(qiáng)調(diào)的是對于傳統(tǒng)的、源于面向過程設(shè)計(jì)思想的層次概念的“倒置”,而“控制反轉(zhuǎn)”強(qiáng)調(diào)的是對程序流程控制權(quán)的反轉(zhuǎn);“依賴倒置”的使用范圍更為寬泛,既可用于對程序流程的描述(如流程的主從和層次關(guān)系),也可用于描述其他擁有概念層次的設(shè)計(jì)模型(如服務(wù)組件與客戶組件、核心模塊與外圍應(yīng)用等),而“控制反轉(zhuǎn)”則僅適用于描述流程控制權(quán)的場合(如算法流程或業(yè)務(wù)流程的控制權(quán))。
      從某種意義上說,我們也可以把“控制反轉(zhuǎn)”看作是“依賴倒置”的一個(gè)特例。例如,用模板方法模式實(shí)現(xiàn)的“控制反轉(zhuǎn)”機(jī)制其實(shí)就是在框架系統(tǒng)和應(yīng)用程序之間抽象出了一個(gè)描述所有算法步驟原型的接口類,框架系統(tǒng)依賴于該接口類定義并實(shí)現(xiàn)程序流程,應(yīng)用程序依賴于該接口類提供具體算法步驟的實(shí)現(xiàn),應(yīng)用程序?qū)蚣芟到y(tǒng)的依賴被“倒置”為二者對抽象接口的依賴。
      總地說來,應(yīng)用程序和框架系統(tǒng)之間的依賴關(guān)系有以下特點(diǎn):
      1. 應(yīng)用程序和框架系統(tǒng)之間實(shí)際上是雙向調(diào)用,雙向依賴的關(guān)系。
      2. 依賴倒置原則可以減弱應(yīng)用程序到框架之間的依賴關(guān)系。
      3. “控制反轉(zhuǎn)”及具體的模板方法模式可以消解框架到應(yīng)用程序之間的依賴關(guān)系,這也是所有框架系統(tǒng)的基礎(chǔ)。
      4. 框架系統(tǒng)可以獨(dú)立重用。

     

    依賴注入(Dependency Injection) 

      在前面的例子里,我們通過“依賴倒置”原則,最大限度地減弱了應(yīng)用程序Copy類和類庫提供的服務(wù)Read,Write之間的依賴關(guān)系。但是,如果需要把Copy()函數(shù)也實(shí)現(xiàn)在類庫中,又會(huì)發(fā)生什么情況呢?假設(shè)在類庫中實(shí)現(xiàn)一個(gè)“服務(wù)類”,“服務(wù)類”提供Copy()方法供應(yīng)用程序使用。應(yīng)用程序使用時(shí),首先創(chuàng)建“服務(wù)類”的實(shí)例,調(diào)用其中的Copy()函數(shù)。“服務(wù)類”的實(shí)例初始化時(shí)會(huì)創(chuàng)建KeyboardReader 和PrinterWriter類的實(shí)例對象。如圖 8所示。

      從圖 8中可以看出,雖然Reader和Writer接口隔離了“服務(wù)類”和具體的Reader和Writer類,使它們之間的耦合降到了最小。但當(dāng) “服務(wù)類”創(chuàng)建具體的Reader和Writer對象時(shí),“服務(wù)類”還是和具體的Reader和Writer對象發(fā)生了依賴關(guān)系——圖 8中用藍(lán)色的虛線描述了這種依賴關(guān)系。
      在這種情況下,如何實(shí)例化具體的Reader和Writer類,同時(shí)又盡量減少服務(wù)類對它們的依賴,就是一個(gè)非常關(guān)鍵的問題了。如果服務(wù)類位于應(yīng)用程序中,這一依賴關(guān)系對我們造成的影響還不算大。但當(dāng)“服務(wù)類”位于需要獨(dú)立發(fā)布的類庫中,它的代碼就不能隨著應(yīng)用程序的變化而改變了。這也意味著,如果“服務(wù)類”過度依賴于具體的Reader和Writer類,用戶就無法自行添加新的Reader和Writer 的實(shí)現(xiàn)了。
      解決這一問題的方法是“依賴注入”,即切斷“服務(wù)類”到具體的Reader和Writer類之間的依賴關(guān)系,而由應(yīng)用程序來注入這一依賴關(guān)系。如圖 9所示。
      在圖 9中,“服務(wù)類”并不負(fù)責(zé)創(chuàng)建具體的Reader和Writer類的實(shí)例對象,而是由應(yīng)用程序來創(chuàng)建。應(yīng)用程序創(chuàng)建“服務(wù)類”的實(shí)例對象時(shí),把具體的Reader和Write對象的引用注入“服務(wù)類”內(nèi)部。這樣,“服務(wù)類”中的代碼就只和抽象接口相關(guān)的了。具體實(shí)現(xiàn)代碼發(fā)生變化時(shí),“服務(wù)類”不會(huì)發(fā)生任何變化。添加新的實(shí)現(xiàn)時(shí),也只需要改變應(yīng)用程序的代碼,就可以定義并使用新的Reader和Writer類,這種依賴注入方式通常也被稱為“構(gòu)造器注入”。
      如果專門為Copy類抽象出一個(gè)注入接口,應(yīng)用程序通過接口注入依賴關(guān)系,這種注入方式通常被稱為“接口注入”。如果為Copy類提供一個(gè)設(shè)值函數(shù),應(yīng)用程序通過調(diào)用設(shè)值函數(shù)來注入依賴關(guān)系,這種依賴注入的方法被稱為“設(shè)值注入”。具體的“接口注入”和“設(shè)值注入”請參考[Martin 2004]。
      PicoContainer和Spring輕量級容器框架都提供了相應(yīng)的機(jī)制來幫助用戶實(shí)現(xiàn)各種不同的“依賴注入”。并且,通過不同的方式,他們也都支持在XML文件中定義依賴關(guān)系,然后由應(yīng)用程序調(diào)用框架來注入依賴關(guān)系,當(dāng)依賴關(guān)系需要發(fā)生變化時(shí),只要修改相應(yīng)的 XML文件即可。
      因此,依賴注入的核心思想是:
      1. 抽象接口隔離了使用者和實(shí)現(xiàn)之間的依賴關(guān)系,但創(chuàng)建具體實(shí)現(xiàn)類的實(shí)例對象仍會(huì)造成對于具體實(shí)現(xiàn)的依賴。
      2. 采用依賴注入可以消除這種創(chuàng)建依賴性。使用依賴注入后,某些類完全是基于抽象接口編寫而成的,這可以最大限度地適應(yīng)需求的變化。

     

    結(jié)論 

      分離接口和實(shí)現(xiàn)是人們有效地控制依賴關(guān)系的最初嘗試,而純粹的抽象接口更好地隔離了相互依賴的兩個(gè)模塊,“依賴倒置”和 “控制反轉(zhuǎn)”原則從不同的角度描述了利用抽象接口消解耦合的動(dòng)機(jī),GoF的設(shè)計(jì)模式正是這一動(dòng)機(jī)的完美體現(xiàn)。具體類的創(chuàng)建過程是另一種常見的依賴關(guān)系,“依賴注入”模式可以把具體類的創(chuàng)建過程集中到合適的位置,這一動(dòng)機(jī)和GoF的創(chuàng)建型模式有相似之處。
      這些原則對我們的實(shí)踐有很好的指導(dǎo)作用,但它們不是圣經(jīng),在不同的場合可能會(huì)有不同的變化,我們應(yīng)該在開發(fā)過程中根據(jù)需求變化的可能性靈活運(yùn)用。

    參考文獻(xiàn) 

    [PicoContainer 2004]  http://www.picocontainer.org/Inversion+of+Control
    [Martin 1996]  The Dependency Inversion Principle, Robert C. Martin, C++ Report, May, 1996, http://www.objectmentor.com/resources/articles/dip.pdf
    [Fowler 2001]  Martin Fowler: Reducing Coupling. IEEE Software 18(4): 102-104 (2001) http://www.martinfowler.com/ieeeSoftware/coupling.pdf
    [Folwer 2004]  Inversion of Control Containers and the Dependency Injection pattern http://martinfowler.com/articles/injection.html
    [透明2004]  透明,Inversion of Control Containers and the Dependency Injection pattern 的譯文http://gigix.blogdriver.com/gigix/inc/DependencyInjection.pdf
    [王詠武, 王詠剛 2004]  王詠武, 王詠剛. 道法自然—面向?qū)ο髮?shí)踐指南. 電子工業(yè)出版社, 2004

    posted on 2006-12-04 16:50 dreamstone 閱讀(1774) 評論(0)  編輯  收藏 所屬分類: 設(shè)計(jì)模式

    主站蜘蛛池模板: 亚洲码和欧洲码一码二码三码 | 毛茸茸bbw亚洲人| 中国一级特黄的片子免费| 亚洲av日韩av无码| 中国在线观看免费高清完整版| 免费国产va视频永久在线观看| 亚洲AV综合色区无码一区| 成年女人午夜毛片免费视频 | 无码日韩精品一区二区免费暖暖| 亚洲人成影院午夜网站| 亚洲精品高清在线| 91精品成人免费国产片| 成人午夜免费视频| 亚洲中文无码a∨在线观看| 亚洲国产综合精品中文字幕| 91福利免费体验区观看区| 免费人成动漫在线播放r18| 久久亚洲AV成人无码| 亚洲性日韩精品一区二区三区| 日本h在线精品免费观看| 免费看黄视频网站| 美女扒开尿口给男人爽免费视频| 久久精品国产精品亚洲艾草网| 在线观看成人免费视频| 无码人妻一区二区三区免费看| 成人精品国产亚洲欧洲| 亚洲妇女水蜜桃av网网站| 亚洲M码 欧洲S码SSS222| 最新欧洲大片免费在线| 今天免费中文字幕视频| 日韩成人毛片高清视频免费看| 亚洲AV一二三区成人影片| 久久亚洲国产精品| 亚洲日本在线观看视频| 在线观着免费观看国产黄| 国色精品卡一卡2卡3卡4卡免费| 最近免费mv在线观看动漫| 日韩在线一区二区三区免费视频 | 亚洲AV日韩AV永久无码绿巨人 | 最新免费jlzzjlzz在线播放| 精品一区二区三区无码免费视频 |