spring概念理解之IOC(控制反轉(zhuǎn))

這是Spring中的有特點(diǎn)的一部份。IoC又被翻譯成“控制反轉(zhuǎn)”,也不知道是誰(shuí)翻譯得這么別扭,感覺(jué)很深?yuàn)W的詞。其實(shí),原理很簡(jiǎn)單,用一句通俗的話來(lái)說(shuō):就是用XML來(lái)定義生成的對(duì)象。IoC其實(shí)是一種設(shè)計(jì)模式,Spring只是實(shí)現(xiàn)了這種設(shè)計(jì)模式。


這種設(shè)計(jì)模式是怎么來(lái)的呢?是實(shí)踐中逐漸形成的。

第一階段:用普通的無(wú)模式來(lái)寫(xiě)Java程序。一般初學(xué)者都要經(jīng)過(guò)這個(gè)階段。

第二階段:頻繁的開(kāi)始使用接口,這時(shí),接口一般都會(huì)伴隨著使用工廠模式。

第三階段:使用IoC模式。工廠模式還不夠好:(1)因?yàn)榈念?lèi)的生成代碼寫(xiě)死在程序里,如果你要換一個(gè)子類(lèi),就要修改工廠方法。(2)一個(gè)接口常常意味著一個(gè)生成工廠,會(huì)多出很多工廠類(lèi)。

可以把IoC模式看做是工廠模式的升華,可以把IoC看作是一個(gè)大工廠,只不過(guò)這個(gè)大工廠里要生成的對(duì)象都是在XML文件中給出定義的,然后利用Java的“反射”編程,根據(jù)XML中給出的類(lèi)名生成相應(yīng)的對(duì)象。從實(shí)現(xiàn)來(lái)看,IoC是把以前在工廠方法里寫(xiě)死的對(duì)象生成代碼,改變?yōu)橛蒟ML文件來(lái)定義,也就是把工廠和對(duì)象生成這兩者獨(dú)立分隔開(kāi)來(lái),目的就是提高靈活性和可維護(hù)性。

這里引用知乎的例子:Spring IoC有什么好處呢?

要了解控制反轉(zhuǎn)( Inversion of Control ), 我覺(jué)得有必要先了解軟件設(shè)計(jì)的一個(gè)重要思想:依賴(lài)倒置原則(Dependency Inversion Principle )。

什么是依賴(lài)倒置原則?假設(shè)我們?cè)O(shè)計(jì)一輛汽車(chē):先設(shè)計(jì)輪子,然后根據(jù)輪子大小設(shè)計(jì)底盤(pán),接著根據(jù)底盤(pán)設(shè)計(jì)車(chē)身,最后根據(jù)車(chē)身設(shè)計(jì)好整個(gè)汽車(chē)。這里就出現(xiàn)了一個(gè)“依賴(lài)”關(guān)系:汽車(chē)依賴(lài)車(chē)身,車(chē)身依賴(lài)底盤(pán),底盤(pán)依賴(lài)輪子。


這樣的設(shè)計(jì)看起來(lái)沒(méi)問(wèn)題,但是可維護(hù)性卻很低。假設(shè)設(shè)計(jì)完工之后,上司卻突然說(shuō)根據(jù)市場(chǎng)需求的變動(dòng),要我們把車(chē)子的輪子設(shè)計(jì)都改大一碼。這下我們就蛋疼了:因?yàn)槲覀兪歉鶕?jù)輪子的尺寸設(shè)計(jì)的底盤(pán),輪子的尺寸一改,底盤(pán)的設(shè)計(jì)就得修改;同樣因?yàn)槲覀兪歉鶕?jù)底盤(pán)設(shè)計(jì)的車(chē)身,那么車(chē)身也得改,同理汽車(chē)設(shè)計(jì)也得改——整個(gè)設(shè)計(jì)幾乎都得改!

我們現(xiàn)在換一種思路。我們先設(shè)計(jì)汽車(chē)的大概樣子,然后根據(jù)汽車(chē)的樣子來(lái)設(shè)計(jì)車(chē)身,根據(jù)車(chē)身來(lái)設(shè)計(jì)底盤(pán),最后根據(jù)底盤(pán)來(lái)設(shè)計(jì)輪子。這時(shí)候,依賴(lài)關(guān)系就倒置過(guò)來(lái)了:輪子依賴(lài)底盤(pán), 底盤(pán)依賴(lài)車(chē)身, 車(chē)身依賴(lài)汽車(chē)。


這時(shí)候,上司再說(shuō)要改動(dòng)輪子的設(shè)計(jì),我們就只需要改動(dòng)輪子的設(shè)計(jì),而不需要?jiǎng)拥妆P(pán),車(chē)身,汽車(chē)的設(shè)計(jì)了。

這就是依賴(lài)倒置原則——把原本的高層建筑依賴(lài)底層建筑“倒置”過(guò)來(lái),變成底層建筑依賴(lài)高層建筑。高層建筑決定需要什么,底層去實(shí)現(xiàn)這樣的需求,但是高層并不用管底層是怎么實(shí)現(xiàn)的。這樣就不會(huì)出現(xiàn)前面的“牽一發(fā)動(dòng)全身”的情況。

控制反轉(zhuǎn)(Inversion of Control) 就是依賴(lài)倒置原則的一種代碼設(shè)計(jì)的思路。具體采用的方法就是所謂的依賴(lài)注入(Dependency Injection)。其實(shí)這些概念初次接觸都會(huì)感到云里霧里的。說(shuō)穿了,這幾種概念的關(guān)系大概如下:


為了理解這幾個(gè)概念,我們還是用上面汽車(chē)的例子。只不過(guò)這次換成代碼。我們先定義四個(gè)Class,車(chē),車(chē)身,底盤(pán),輪胎。然后初始化這輛車(chē),最后跑這輛車(chē)。代碼結(jié)構(gòu)如下:


這樣,就相當(dāng)于上面第一個(gè)例子,上層建筑依賴(lài)下層建筑——每一個(gè)類(lèi)的構(gòu)造函數(shù)都直接調(diào)用了底層代碼的構(gòu)造函數(shù)。假設(shè)我們需要改動(dòng)一下輪胎(Tire)類(lèi),把它的尺寸變成動(dòng)態(tài)的,而不是一直都是30。我們需要這樣改:


由于我們修改了輪胎的定義,為了讓整個(gè)程序正常運(yùn)行,我們需要做以下改動(dòng):


由此我們可以看到,僅僅是為了修改輪胎的構(gòu)造函數(shù),這種設(shè)計(jì)卻需要修改整個(gè)上層所有類(lèi)的構(gòu)造函數(shù)!在軟件工程中,這樣的設(shè)計(jì)幾乎是不可維護(hù)的——在實(shí)際工程項(xiàng)目中,有的類(lèi)可能會(huì)是幾千個(gè)類(lèi)的底層,如果每次修改這個(gè)類(lèi),我們都要修改所有以它作為依賴(lài)的類(lèi),那軟件的維護(hù)成本就太高了。

所以我們需要進(jìn)行控制反轉(zhuǎn)(IoC),及上層控制下層,而不是下層控制著上層。我們用依賴(lài)注入(Dependency Injection)這種方式來(lái)實(shí)現(xiàn)控制反轉(zhuǎn)。所謂依賴(lài)注入,就是把底層類(lèi)作為參數(shù)傳入上層類(lèi),實(shí)現(xiàn)上層類(lèi)對(duì)下層類(lèi)的“控制”。這里我們用構(gòu)造方法傳遞的依賴(lài)注入方式重新寫(xiě)車(chē)類(lèi)的定義:


這里我們?cè)侔演喬コ叽缱兂蓜?dòng)態(tài)的,同樣為了讓整個(gè)系統(tǒng)順利運(yùn)行,我們需要做如下修改:


看到?jīng)]?這里我只需要修改輪胎類(lèi)就行了,不用修改其他任何上層類(lèi)。這顯然是更容易維護(hù)的代碼。不僅如此,在實(shí)際的工程中,這種設(shè)計(jì)模式還有利于不同組的協(xié)同合作和單元測(cè)試:比如開(kāi)發(fā)這四個(gè)類(lèi)的分別是四個(gè)不同的組,那么只要定義好了接口,四個(gè)不同的組可以同時(shí)進(jìn)行開(kāi)發(fā)而不相互受限制;而對(duì)于單元測(cè)試,如果我們要寫(xiě)Car類(lèi)的單元測(cè)試,就只需要Mock一下Framework類(lèi)傳入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再來(lái)構(gòu)造Car。

這里我們是采用的構(gòu)造函數(shù)傳入的方式進(jìn)行的依賴(lài)注入。其實(shí)還有另外兩種方法:Setter傳遞和接口傳遞。這里就不多講了,核心思路都是一樣的,都是為了實(shí)現(xiàn)控制反轉(zhuǎn)。


看到這里你應(yīng)該能理解什么控制反轉(zhuǎn)和依賴(lài)注入了。那什么是控制反轉(zhuǎn)容器(IoC Container)呢?其實(shí)上面的例子中,對(duì)車(chē)類(lèi)進(jìn)行初始化的那段代碼發(fā)生的地方,就是控制反轉(zhuǎn)容器。


顯然你也應(yīng)該觀察到了,因?yàn)椴捎昧艘蕾?lài)注入,在初始化的過(guò)程中就不可避免的會(huì)寫(xiě)大量的new。這里IoC容器就解決了這個(gè)問(wèn)題。這個(gè)容器可以自動(dòng)對(duì)你的代碼進(jìn)行初始化,你只需要維護(hù)一個(gè)Configuration(可以是xml可以是一段代碼),而不用每次初始化一輛車(chē)都要親手去寫(xiě)那一大段初始化的代碼。這是引入IoC Container的第一個(gè)好處。

IoC Container的第二個(gè)好處是:我們?cè)趧?chuàng)建實(shí)例的時(shí)候不需要了解其中的細(xì)節(jié)。在上面的例子中,我們自己手動(dòng)創(chuàng)建一個(gè)車(chē)instance時(shí)候,是從底層往上層new的:


這個(gè)過(guò)程中,我們需要了解整個(gè)Car/Framework/Bottom/Tire類(lèi)構(gòu)造函數(shù)是怎么定義的,才能一步一步new/注入。

而IoC Container在進(jìn)行這個(gè)工作的時(shí)候是反過(guò)來(lái)的,它先從最上層開(kāi)始往下找依賴(lài)關(guān)系,到達(dá)最底層之后再往上一步一步new(有點(diǎn)像深度優(yōu)先遍歷):


這里IoC Container可以直接隱藏具體的創(chuàng)建實(shí)例的細(xì)節(jié),在我們來(lái)看它就像一個(gè)工廠:


我們就像是工廠的客戶(hù)。我們只需要向工廠請(qǐng)求一個(gè)Car實(shí)例,然后它就給我們按照Config創(chuàng)建了一個(gè)Car實(shí)例。我們完全不用管這個(gè)Car實(shí)例是怎么一步一步被創(chuàng)建出來(lái)。

實(shí)際項(xiàng)目中,有的Service Class可能是十年前寫(xiě)的,有幾百個(gè)類(lèi)作為它的底層。假設(shè)我們新寫(xiě)的一個(gè)API需要實(shí)例化這個(gè)Service,我們總不可能回頭去搞清楚這幾百個(gè)類(lèi)的構(gòu)造函數(shù)吧?IoC Container的這個(gè)特性就很完美的解決了這類(lèi)問(wèn)題——因?yàn)檫@個(gè)架構(gòu)要求你在寫(xiě)class的時(shí)候需要寫(xiě)相應(yīng)的Config文件,所以你要初始化很久以前的Service類(lèi)的時(shí)候,前人都已經(jīng)寫(xiě)好了Config文件,你直接在需要用的地方注入這個(gè)Service就可以了。這大大增加了項(xiàng)目的可維護(hù)性且降低了開(kāi)發(fā)難度。

這里只是很粗略的講了一下我自己對(duì)IoC和DI的理解。主要的目的是在于最大限度避免晦澀難懂的專(zhuān)業(yè)詞匯,用盡量簡(jiǎn)潔,通俗,直觀的例子來(lái)解釋這些概念。如果讓大家能有一個(gè)類(lèi)似“哦!原來(lái)就是這么個(gè)玩意嘛!”的印象,我覺(jué)得就OK了。想要深入了解的話,可以上網(wǎng)查閱一些更權(quán)威的資料。這里推薦一下 Dependency injectionInversion of Control Containers and the Dependency Injection pattern 這兩篇文章,講的很好很詳細(xì)。

IoC最大的好處是什么?因?yàn)榘褜?duì)象生成放在了XML里定義,所以當(dāng)我們需要換一個(gè)實(shí)現(xiàn)子類(lèi)將會(huì)變成很簡(jiǎn)單(一般這樣的對(duì)象都是現(xiàn)實(shí)于某種接口的),只要修改XML就可以了,這樣我們甚至可以實(shí)現(xiàn)對(duì)象的熱插撥(有點(diǎn)象USB接口和SCIS硬盤(pán)了)。

IoC最大的缺點(diǎn)是什么?(1)生成一個(gè)對(duì)象的步驟變復(fù)雜了(其實(shí)上操作上還是挺簡(jiǎn)單的),對(duì)于不習(xí)慣這種方式的人,會(huì)覺(jué)得有些別扭和不直觀。(2)對(duì)象生成因?yàn)槭鞘褂梅瓷渚幊蹋谛噬嫌行p耗。但相對(duì)于IoC提高的維護(hù)性和靈活性來(lái)說(shuō),這點(diǎn)損耗是微不足道的,除非某對(duì)象的生成對(duì)效率要求特別高。(3)缺少I(mǎi)DE重構(gòu)操作的支持,如果在Eclipse要對(duì)類(lèi)改名,那么你還需要去XML文件里手工去改了,這似乎是所有XML方式的缺憾所在。

總的來(lái)說(shuō)IoC無(wú)論原理和實(shí)現(xiàn)都還算是很簡(jiǎn)單的。一些人曾認(rèn)為IoC沒(méi)什么實(shí)際作用,這種說(shuō)法是可以理解的,因?yàn)槿绻阍诰幊讨泻苌偈褂媒涌?,或很少使用工廠模式,那么你根本就沒(méi)有使用IoC的強(qiáng)烈需要,也不會(huì)體會(huì)到IoC可貴之處。有些人也說(shuō)要消除工廠模式、單例模式,但是都語(yǔ)焉不詳、人云亦云。但如果你看到IoC模式和用上Spring,那么工廠模式和單例模式的確基本上可以不用了。但它消失了嗎?沒(méi)有!Spring的IoC實(shí)現(xiàn)本身就是一個(gè)大工廠,其中也包含了單例對(duì)象生成方式,只要用一個(gè)設(shè)置就可以讓對(duì)象生成由普通方式變單一實(shí)例方式,非常之簡(jiǎn)單。

?? 總結(jié):

?? (1)IoC原理很簡(jiǎn)單,作用的針對(duì)性也很強(qiáng),不要把它看得很玄乎。

?? (2)要理解IoC,首先要了解“工廠、接口、反射”這些概念。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載鏈接:http://blog.xiaohansong.com/2015/10/21/IoC-and-DI/#h...
    ALEXIRC閱讀 50,967評(píng)論 3 116
  • 我先自己總結(jié)一下 IOC: 原來(lái)上層依賴(lài)下層,下層改變,上層也要大量的修改,不好.所以上層中拿著下層的對(duì)象,下層改...
    羅曼蒂克閱讀 10,538評(píng)論 6 37
  • 上個(gè)月,我去看那個(gè)抱著吉他唱歌、現(xiàn)改道畫(huà)畫(huà)的跨界藝術(shù)家艾敬在紐約舉辦的個(gè)人畫(huà)展。出發(fā)前我特意把內(nèi)置國(guó)內(nèi)電話卡...
    閑云6556閱讀 505評(píng)論 0 0
  • 兔小灰其實(shí)只是個(gè)小慫蛋。 他愛(ài)上了一只兔子——兔小白。 他每天都假裝遇見(jiàn)兔小白,偶爾間與她對(duì)上目光有害臊的別過(guò)頭去...
    夏青殊閱讀 613評(píng)論 0 2
  • 今天第一次帶76人企業(yè)退休老年團(tuán)出行,玉渡山一日行。前一天有焦慮,有緊張, 看見(jiàn)了。 看見(jiàn)自己反復(fù)看鬧鐘的時(shí)間,看...
    尚靈心閱讀 777評(píng)論 3 5

友情鏈接更多精彩內(nèi)容