? ? 今天又重新開始看 Spring 的知識點(diǎn),在知乎上看到一個大佬對 ioc 比較通俗易懂的解釋早歇,看完后覺得有點(diǎn)豁然開朗的感覺倾芝,畢竟之前對 ioc 的理解也僅限于它就是為了方便我們創(chuàng)建 bean 而存在的,卻不知道它真正的設(shè)計原理缺前,說起來有點(diǎn)慚愧蛀醉,現(xiàn)在把大佬的回答結(jié)合自己的理解寫一下悬襟。
原文出處?作者:Mingqi
一衅码、依賴倒置原則( Dependency Inversion Principle )
? ? 在理解什么是 ioc(控制反轉(zhuǎn))之前,首先要了解一個軟件設(shè)計的思想:依賴倒置原則脊岳。
? ??什么是依賴倒置原則逝段?假設(shè)我們設(shè)計一輛汽車:先設(shè)計輪子垛玻,然后根據(jù)輪子大小設(shè)計底盤,接著根據(jù)底盤設(shè)計車身奶躯,最后根據(jù)車身設(shè)計好整個汽車帚桩。這里就出現(xiàn)了一個“依賴”關(guān)系:汽車依賴車身,車身依賴底盤嘹黔,底盤依賴輪子账嚎。
? ??????這樣的設(shè)計看起來沒問題,但是可維護(hù)性卻很低儡蔓。假設(shè)設(shè)計完工之后郭蕉,上司卻突然說根據(jù)市場需求的變動,要我們把車子的輪子設(shè)計都改大一碼喂江。這下我們就蛋疼了:因為我們是根據(jù)輪子的尺寸設(shè)計的底盤召锈,輪子的尺寸一改,底盤的設(shè)計就得修改获询;同樣因為我們是根據(jù)底盤設(shè)計的車身涨岁,那么車身也得改,同理汽車設(shè)計也得改——整個設(shè)計幾乎都得改吉嚣!
? ??我們現(xiàn)在換一種思路梢薪。我們先設(shè)計汽車的大概樣子,然后根據(jù)汽車的樣子來設(shè)計車身尝哆,根據(jù)車身來設(shè)計底盤沮尿,最后根據(jù)底盤來設(shè)計輪子。這時候较解,依賴關(guān)系就倒置過來了:輪子依賴底盤畜疾, 底盤依賴車身, 車身依賴汽車印衔。
? ??這時候啡捶,上司再說要改動輪子的設(shè)計,我們就只需要改動輪子的設(shè)計奸焙,而不需要動底盤瞎暑,車身,汽車的設(shè)計了与帆。
? ??這就是依賴倒置原則——把原本的高層建筑依賴底層建筑“倒置”過來了赌,變成底層建筑依賴高層建筑。高層建筑決定需要什么玄糟,底層去實(shí)現(xiàn)這樣的需求勿她,但是高層并不用管底層是怎么實(shí)現(xiàn)的。這樣就不會出現(xiàn)前面的“牽一發(fā)動全身”的情況阵翎。
二逢并、控制反轉(zhuǎn)(Inversion of Control)
? ??控制反轉(zhuǎn)(Inversion of Control)?就是依賴倒置原則的一種代碼設(shè)計的思路之剧。具體采用的方法就是所謂的依賴注入(Dependency Injection)。其實(shí)這些概念初次接觸都會感到云里霧里的砍聊。說穿了背稼,這幾種概念的關(guān)系大概如下:
? ??為了理解這幾個概念,我們還是用上面汽車的例子玻蝌。只不過這次換成代碼蟹肘。我們先定義四個Class,車俯树,車身疆前,底盤,輪胎聘萨。然后初始化這輛車竹椒,最后跑這輛車。代碼結(jié)構(gòu)如下:
? ??這樣米辐,就相當(dāng)于上面第一個例子胸完,上層建筑依賴下層建筑——每一個類的構(gòu)造函數(shù)都直接調(diào)用了底層代碼的構(gòu)造函數(shù)。假設(shè)我們需要改動一下輪胎(Tire)類翘贮,把它的尺寸變成動態(tài)的赊窥,而不是一直都是30。我們需要這樣改:
? ??由于我們修改了輪胎的定義狸页,為了讓整個程序正常運(yùn)行锨能,我們需要做以下改動:
? ??由此我們可以看到,僅僅是為了修改輪胎的構(gòu)造函數(shù)芍耘,這種設(shè)計卻需要修改整個上層所有類的構(gòu)造函數(shù)址遇!在軟件工程中,這樣的設(shè)計幾乎是不可維護(hù)的——在實(shí)際工程項目中斋竞,有的類可能會是幾千個類的底層倔约,如果每次修改這個類,我們都要修改所有以它作為依賴的類坝初,那軟件的維護(hù)成本就太高了浸剩!
? ??所以我們需要進(jìn)行控制反轉(zhuǎn)(IoC),及上層控制下層鳄袍,而不是下層控制著上層绢要。我們用依賴注入(Dependency Injection)這種方式來實(shí)現(xiàn)控制反轉(zhuǎn)。所謂依賴注入拗小,就是把底層類作為參數(shù)傳入上層類重罪,實(shí)現(xiàn)上層類對下層類的“控制”。這里我們用構(gòu)造方法傳遞的依賴注入方式重新寫車類的定義:
這里我們再把輪胎尺寸變成動態(tài)的,同樣為了讓整個系統(tǒng)順利運(yùn)行蛆封,我們需要做如下修改:
????這里我只需要修改輪胎類就行了,不用修改其他任何上層類勾栗。這顯然是更容易維護(hù)的代碼惨篱。不僅如此,在實(shí)際的工程中围俘,這種設(shè)計模式還有利于不同組的協(xié)同合作和單元測試:比如開發(fā)這四個類的分別是四個不同的組砸讳,那么只要定義好了接口,四個不同的組可以同時進(jìn)行開發(fā)而不相互受限制界牡;而對于單元測試簿寂,如果我們要寫Car類的單元測試胳泉,就只需要Mock一下Framework類傳入Car就行了袜匿,而不用把Framework, Bottom, Tire全部new一遍再來構(gòu)造Car。
? ??這里我們是采用的構(gòu)造函數(shù)傳入的方式進(jìn)行的依賴注入侥锦。其實(shí)還有另外兩種方法:Setter傳遞和接口傳遞挽荠。這里就不多講了克胳,核心思路都是一樣的,都是為了實(shí)現(xiàn)控制反轉(zhuǎn)圈匆。
三漠另、 控制反轉(zhuǎn)容器(IoC Container)
? ??其實(shí)上面的例子中,對車類進(jìn)行初始化的那段代碼發(fā)生的地方跃赚,就是控制反轉(zhuǎn)容器笆搓。
? ??因為采用了依賴注入,在初始化的過程中就不可避免的會寫大量的new纬傲。這里IoC容器就解決了這個問題满败。這個容器可以自動對你的代碼進(jìn)行初始化,你只需要維護(hù)一個Configuration(可以是xml可以是一段代碼)叹括,而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼葫录。這是引入IoC Container的第一個好處。
? ??IoC Container的第二個好處是:我們在創(chuàng)建實(shí)例的時候不需要了解其中的細(xì)節(jié)领猾。在上面的例子中米同,我們自己手動創(chuàng)建一個車instance時候,是從底層往上層new的:
????這個過程中摔竿,我們需要了解整個Car/Framework/Bottom/Tire類構(gòu)造函數(shù)是怎么定義的面粮,才能一步一步new/注入
? ??而IoC Container在進(jìn)行這個工作的時候是反過來的,它先從最上層開始往下找依賴關(guān)系继低,到達(dá)最底層之后再往上一步一步new(有點(diǎn)像深度優(yōu)先遍歷):
? ??這里IoC Container可以直接隱藏具體的創(chuàng)建實(shí)例的細(xì)節(jié)熬苍,在我們來看它就像一個工廠:
????我們就像是工廠的客戶。我們只需要向工廠請求一個Car實(shí)例,然后它就給我們按照Config創(chuàng)建了一個Car實(shí)例柴底。我們完全不用管這個Car實(shí)例是怎么一步一步被創(chuàng)建出來婿脸。
? ??實(shí)際項目中,有的Service Class可能是十年前寫的柄驻,有幾百個類作為它的底層狐树。假設(shè)我們新寫的一個API需要實(shí)例化這個Service,我們總不可能回頭去搞清楚這幾百個類的構(gòu)造函數(shù)吧鸿脓?IoC Container的這個特性就很完美的解決了這類問題——因為這個架構(gòu)要求你在寫class的時候需要寫相應(yīng)的Config文件抑钟,所以你要初始化很久以前的Service類的時候,前人都已經(jīng)寫好了Config文件野哭,你直接在需要用的地方注入這個Service就可以了在塔。這大大增加了項目的可維護(hù)性且降低了開發(fā)難度。