OOPAA

          Focusing on OO, Patterns, Architecture, and Agile
          posts - 29, comments - 75, trackbacks - 0, articles - 0
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          DIP 沉思錄

          Posted on 2008-11-02 17:37 mingj 閱讀(1852) 評(píng)論(2)  編輯  收藏 所屬分類: OO 面向?qū)ο?/a>
          Dependency Injection 這個(gè)名詞,是在 Martin Fowler 的《Inversion of Control Containers and the Dependency Injection pattern》文章之后才廣為人知。在文章中,Martin 解釋了當(dāng)時(shí)初起流行的 IOC 概念:為了消除應(yīng)用程序?qū)Σ寮?shí)現(xiàn)的依賴,程序的主控權(quán)從應(yīng)用程序移到了框架。為了讓 IOC 概念不那么令人迷惑,Martin 把流行的幾種 IOC Container 實(shí)現(xiàn)模式命名為 Dependency Injection Pattern(DIP,下文簡(jiǎn)稱 DI 模式)。很明顯,DI 的定義更準(zhǔn)確形象,而且因?yàn)?Martin 在軟件開發(fā)社區(qū)巨大的影響力,DI 模式以及 spring framework 作為其最成功的實(shí)現(xiàn)之一很快被軟件開發(fā)社區(qū)接受并成為日常開發(fā)的必備利器。

          “依賴注入”給我們代碼的編寫,特別是測(cè)試代碼的編寫帶來(lái)了很多好處。借助于 DI 模式,我們可以將服務(wù)的依賴聲明和具體實(shí)現(xiàn)相分離,通過(guò)配置不同的服務(wù)實(shí)例交給框架管理,來(lái)讓應(yīng)用程序獲得良好的靈活性和柔性。

          比如我們有這樣的代碼:

          public void hello() {
              Foo foo 
          = new Foo();
              foo.sayHello();
              
          //
          }


          這種情況下,hello() 方法就依賴于 Foo 類的 sayHello() 方法實(shí)現(xiàn),會(huì)給測(cè)試帶來(lái)了很多的不穩(wěn)定性,比如耗時(shí)、服務(wù)異常之類。如果依照 DI 模式來(lái)重構(gòu),這段代碼就會(huì)變成這樣:

          public void hello(Foo foo) {
              foo.sayHello();
              
          //
          }

          在測(cè)試時(shí)我們就可以使用行為確定符合預(yù)期的‘mock’的 Foo 類來(lái)代替實(shí)際的 Foo 類,從而將測(cè)試關(guān)注點(diǎn)聚集到 hello() 方法,也避免了 Foo 類 sayHello() 方法的具體實(shí)現(xiàn)對(duì)測(cè)試可能帶來(lái)的影響。

          以上就是 DIP 最常見也是最令人熟悉的一層涵義,從 IOC、spring framework 開始接受 DIP,自然而然會(huì)把 DIP 等同于 DI 模式,但其實(shí)它還有另一層涵義。在《Agile Software Developement:principles,Patterns,and Practices》的第11章 依賴倒置原則,給出了 DIP 的另一種解讀:DIP —— 依賴倒置原則,這里的 DIP 就不再是 Dependency Injection Pattern(依賴注入模式),而是 Dependency Inversion Principle(依賴倒置原則,以下簡(jiǎn)稱 DI 原則)。DI 原則主要包括下面兩條啟發(fā)式規(guī)則:
              A. 高層模塊不應(yīng)依賴于低層模塊。二者都應(yīng)依賴于抽象
              B. 抽象不應(yīng)依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)依賴于抽象

          人們通常會(huì)用好萊塢法則來(lái)詮釋啟發(fā)規(guī)則 A:“Don't call us, we'll call you.”其中的 we/us,就是指高層模塊,you 則是低層模塊,高層模塊來(lái)決定低層的模塊,就像好萊塢制片人最終掌握著演員上鏡與否的生殺大權(quán)。

          我們來(lái)看看軟件系統(tǒng)中常見的類關(guān)系,高層的 Policy Layer 使用了低層的 Mechanism Layer,而低層的 Mechanism Layer 又使用了更低層的 Utility Layer(見圖1)


          圖1中的依賴關(guān)系是傳遞的:高層的 Policy Layer 對(duì)于其下一直到 Utility Layer 的改動(dòng)都是敏感的,一旦我們修改了低層模塊的實(shí)現(xiàn),高層模塊不得不也修改相應(yīng)的實(shí)現(xiàn)來(lái)適應(yīng)低層模塊的修改。這種依賴關(guān)系,與軟件復(fù)用的目標(biāo)是相悖的,而且也不符合實(shí)際的業(yè)務(wù)變化。我們通常會(huì)需要切換低層的實(shí)現(xiàn)方式或者版本,而很少會(huì)去修改高層的業(yè)務(wù)。比如有一個(gè)證書申請(qǐng)發(fā)放系統(tǒng),需要使用異步的消息機(jī)制來(lái)處理用戶請(qǐng)求。客戶可能會(huì)要求把底層的 MessageQueue 從 IIS 切換成 ActiveQ,但高層的證書申請(qǐng)發(fā)放的業(yè)務(wù)流程是穩(wěn)定的,不會(huì)因低層基礎(chǔ)服務(wù)的改變而作出修改。

          所以,為了應(yīng)付現(xiàn)實(shí)中的變化,也為了向高層屏蔽這些變化,我們通常會(huì)給低層模塊抽象出接口,作為高層和低層之間的契約。這樣,高層模塊就應(yīng)該只與低層模塊的接口打交道,不再關(guān)心低層模塊的實(shí)現(xiàn)細(xì)節(jié)。而低層模塊的實(shí)現(xiàn),我們可以通過(guò)配置文件由框架來(lái)切換,不影響到高層的行為。說(shuō)到這里,大家應(yīng)該可以看到 Spring 的影子了。此時(shí)類關(guān)系圖就變成了下圖(圖2)


          嗯,現(xiàn)在的類關(guān)系已經(jīng)遵循接口依賴了,甚至加上了框架的 DI 模式,系統(tǒng)也具有了一定的靈活性和易改變性,看起來(lái)很像大多數(shù)的系統(tǒng)了。但是,這是不是就是符合 DI 原則呢?我們可以看到,這里的接口通常是由低層模塊來(lái)定義和派生,也就是低層模塊抽象出來(lái)提供給外界調(diào)用的服務(wù)接口。高層模塊其實(shí)還是依賴于低層模塊,只是這次高層模塊產(chǎn)生依賴的是低層模塊里面聲明的接口。想起曾經(jīng)在 javaeye 上看到一篇帖子,作者抱怨為什么 java 不提供 extracts 關(guān)鍵字,這樣就可以由框架或者容器在具體類上抽出接口定義(與 implements 對(duì)應(yīng)),省得手工創(chuàng)建這些接口?;蛟S這是目前依賴注入框架帶來(lái)的誤區(qū)吧:使用人員不理解 DIP 的本意,單純是為框架的約束而創(chuàng)建。此時(shí)聲明的類關(guān)系顯然還是沒有完全體現(xiàn) DI 原則的精髓。

          在書中 Robert 進(jìn)一步對(duì) DI 原則給出了解釋:“請(qǐng)注意這里的倒置不僅僅是依賴關(guān)系的倒置,也是接口所有權(quán)的倒置。我們通常會(huì)認(rèn)為工具庫(kù)應(yīng)該擁有它們自己的接口,但是應(yīng)用 DIP (DI 原則)時(shí),我們發(fā)現(xiàn)往往是消費(fèi)者擁有抽象接口,而它們的服務(wù)者則從這些接口派生。”基于這種思路,前面例子更符合面向?qū)ο笏枷氲膶哟侮P(guān)系圖如下(圖3)


          圖3和圖2的區(qū)別主要在接口的所有權(quán):高層模塊應(yīng)該擁有接口所有權(quán),低層模塊派生自高層模塊里定義的接口。這就意味著:
              1. 高層模塊引用的接口定義應(yīng)該和高層模塊的其他類放在一起
              2. 高層模塊的復(fù)用是把高層擁有的所有類和接口定義作為一個(gè)整體來(lái)復(fù)用的
              3. 接口定義的改變只有根據(jù)高層模塊的需要才進(jìn)行的,而不是低層模塊

          OK,直到現(xiàn)在,我們才能說(shuō) DI 原則原來(lái)是這個(gè)意思,否則,體會(huì)不到就有可能誤人誤己。Robert 在本章的結(jié)論中說(shuō)“事實(shí)上,這種依賴關(guān)系的倒置正是好的面向?qū)ο笤O(shè)計(jì)的標(biāo)志所在,使用何種語(yǔ)言來(lái)編寫程序是無(wú)關(guān)緊要的。如果程序的依賴關(guān)系是倒置的,它就是面向?qū)ο蟮脑O(shè)計(jì)。如果程序的依賴關(guān)系不是倒置的,它就是過(guò)程化的設(shè)計(jì)。”信哉此言!

          那么,要按照 OOA & OOD 的設(shè)計(jì)思路來(lái)進(jìn)行系統(tǒng)設(shè)計(jì),DI 原則對(duì)我們有什么幫助呢?其實(shí),DI 原則不僅僅是抽象的原則,而且是可以啟發(fā)推導(dǎo)出出種種具體的實(shí)踐。我們來(lái)看看對(duì) OOA & OOD 的幫助。因?yàn)橐蕾囮P(guān)系是倒置的,就可以通過(guò)對(duì)高層策略的抽象和定義驅(qū)動(dòng)出低層服務(wù)者的接口。以此類推,直到把最底層的模塊設(shè)計(jì)出為止。那什么是高層策略呢?怎么找出潛在的抽象?書中同樣給出了答案:“它是應(yīng)用背后的抽象,是那些不隨具體細(xì)節(jié)的改變而改變的真理。它是系統(tǒng)內(nèi)部的系統(tǒng)——它是隱喻(metaphore)”更有詳細(xì)的具體實(shí)踐,敬請(qǐng)關(guān)注本博其他文章。


          References:
          Inversion of Control Containers and the Dependency Injection pattern,http://www.martinfowler.com/articles/injection.html,Martin Fowler
          控制反轉(zhuǎn)與依賴注入模式,http://gigix.blogdriver.com/diary/gigix/inc/DependencyInjection.pdf,熊節(jié) 譯
          《Agile Software Developement:principles,Patterns,and Practices》,Robert Fowler 著,鄧輝 譯,孟巖 審


          評(píng)論

          # re: DIP 沉思錄  回復(fù)  更多評(píng)論   

          2008-11-02 20:02 by paul xu
          我覺得可以這樣理解:高層稱為使用者,低層稱為提供者,oo的設(shè)計(jì)應(yīng)該是首先有使用者,(因?yàn)橹挥惺褂谜卟胖雷约盒枰裁矗?,根?jù)使用者的需要,再去構(gòu)建低層提供者

          # re: DIP 沉思錄  回復(fù)  更多評(píng)論   

          2008-11-02 20:30 by mingj
          @paul xu
          對(duì), 消費(fèi)者以及服務(wù)者提供了類似的定義
          其實(shí)這也是提供了一種構(gòu)建系統(tǒng)的方法

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 广州市| 安徽省| 连江县| 祁门县| 海林市| 新密市| 天门市| 威海市| 长治县| 新绛县| 富民县| 含山县| 革吉县| 丹阳市| 观塘区| 五华县| 肥西县| 开江县| 台北市| 连江县| 靖西县| 石景山区| 正安县| 怀来县| 玉环县| 兴仁县| 丘北县| 新巴尔虎左旗| 汨罗市| 蒲江县| 武宣县| 湖北省| 宁南县| 鄯善县| 伊宁县| 义马市| 天柱县| 安达市| 东乡| 龙山县| 富阳市|