IoC 這樣理解更簡(jiǎn)單
小伙伴們大家好,我是小牛肉,最近看 IoC 的源碼頭都看大了 ??,先出一篇讀起來比較輕松的描述 IoC 概念的文章,原文來自這里 https://www.zhihu.com/question/23277575/answer/169698662,可能比喻不是特別貼切,不過在幫助理解 IoC 上面,還是比較通俗易懂的,然后我做了一些修改整理和總結(jié),背誦版在文末~
依賴倒置原則
要了解 控制反轉(zhuǎn)( Inversion of Control,IoC), 我覺得有必要先了解軟件設(shè)計(jì)的一個(gè)重要思想:依賴倒置原則(Dependency Inversion Principle )。
什么是依賴倒置原則?
假設(shè)我們?cè)O(shè)計(jì)一輛汽車:先設(shè)計(jì)輪子,然后根據(jù)輪子大小設(shè)計(jì)底盤,接著根據(jù)底盤設(shè)計(jì)車身,最后根據(jù)車身設(shè)計(jì)好整個(gè)汽車。這里就出現(xiàn)了一個(gè) “依賴” 關(guān)系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。

這樣的設(shè)計(jì)看起來沒問題,但是可維護(hù)性卻很低。
假設(shè)設(shè)計(jì)完工之后,上司卻突然說根據(jù)市場(chǎng)需求的變動(dòng),要我們把車子的輪子設(shè)計(jì)都改大一碼。這下我們就蛋疼了:因?yàn)槲覀兪歉鶕?jù)輪子的尺寸設(shè)計(jì)的底盤,輪子的尺寸一改,底盤的設(shè)計(jì)就得修改;同樣因?yàn)槲覀兪歉鶕?jù)底盤設(shè)計(jì)的車身,那么車身也得改,同理汽車設(shè)計(jì)也得改——整個(gè)設(shè)計(jì)幾乎都得改!
我們現(xiàn)在換一種思路。我們先設(shè)計(jì)汽車的大概樣子,然后根據(jù)汽車的樣子來設(shè)計(jì)車身,根據(jù)車身來設(shè)計(jì)底盤,最后根據(jù)底盤來設(shè)計(jì)輪子。這時(shí)候,依賴關(guān)系就倒置過來了:輪子依賴底盤,底盤依賴車身,車身依賴汽車。

這時(shí)候,上司再說要改動(dòng)輪子的設(shè)計(jì),我們就只需要改動(dòng)輪子的設(shè)計(jì),而不需要?jiǎng)拥妆P,車身,汽車的設(shè)計(jì)了。
這就是依賴倒置原則 — 把原本的高層建筑依賴底層建筑“倒置”過來,變成底層建筑依賴高層建筑。高層建筑決定需要什么,底層去實(shí)現(xiàn)這樣的需求,但是高層并不用管底層是怎么實(shí)現(xiàn)的。這樣就不會(huì)出現(xiàn)前面的“牽一發(fā)動(dòng)全身”的情況。
控制反轉(zhuǎn) IoC
控制反轉(zhuǎn)(Inversion of Control) 就是依賴倒置原則的一種具體的設(shè)計(jì)思路,或者說是一種可行的思想。而 IoC 的具體實(shí)現(xiàn)方法呢,就是 依賴注入(Dependency Injection)。這幾種概念的關(guān)系大概如下:

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

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

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

由此我們可以看到,僅僅是為了修改輪胎的構(gòu)造函數(shù),這種設(shè)計(jì)卻需要修改整個(gè)上層所有類的構(gòu)造函數(shù)!在實(shí)際工程項(xiàng)目中,有的類可能會(huì)是幾千個(gè)類的底層,如果每次修改這個(gè)類,我們都要修改所有以它作為依賴的類,那軟件的維護(hù)成本就太高了。
所以我們需要進(jìn)行控制反轉(zhuǎn)(IoC),及上層控制下層,而不是下層控制著上層。我們用依賴注入(Dependency Injection)這種方式來實(shí)現(xiàn)控制反轉(zhuǎn)。所謂依賴注入,就是把底層類作為參數(shù)傳入上層類,實(shí)現(xiàn)上層類對(duì)下層類的“控制”。這里我們用構(gòu)造方法傳遞的依賴注入方式重新寫車類的定義:

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

看到?jīng)]?這里**我只需要修改輪胎類就行了,不用修改其他任何上層類。**這顯然是更容易維護(hù)的代碼。

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

顯然你也應(yīng)該觀察到了,因?yàn)椴捎昧艘蕾囎⑷耄诔跏蓟倪^程中就不可避免的會(huì)寫大量的 new。這里 IoC 容器就解決了這個(gè)問題。這個(gè)容器可以自動(dòng)對(duì)你的代碼進(jìn)行初始化,你只需要維護(hù)一個(gè)配置文件,而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼。這是引入 IoC Container 的第一個(gè)好處。
IoC Container的第二個(gè)好處是:我們?cè)趧?chuàng)建實(shí)例的時(shí)候不需要了解其中的細(xì)節(jié)。在上面的例子中,我們自己手動(dòng)創(chuàng)建一個(gè)車 instance 時(shí)候,是從底層往上層 new 的:

這個(gè)過程中,我們需要了解整個(gè) Car/Framework/Bottom/Tire 類構(gòu)造函數(shù)是怎么定義的,才能一步一步 new/注入。
而 IoC Container 在進(jìn)行這個(gè)工作的時(shí)候是反過來的,它先從最上層開始往下找依賴關(guān)系,到達(dá)最底層之后再往上一步一步 new(有點(diǎn)像深度優(yōu)先遍歷):

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

我們就像是工廠的客戶。我們只需要向工廠請(qǐng)求一個(gè) Car 實(shí)例,然后它就給我們按照 Config 創(chuàng)建了一個(gè) Car 實(shí)例。我們完全不用管這個(gè) Car 實(shí)例是怎么一步一步被創(chuàng)建出來。
實(shí)際項(xiàng)目中,有的 Service Class 可能是十年前寫的,有幾百個(gè)類作為它的底層。假設(shè)我們新寫的一個(gè)API需要實(shí)例化這個(gè)Service,我們總不可能回頭去搞清楚這幾百個(gè)類的構(gòu)造函數(shù)吧?
IoC Container 的這個(gè)特性就很完美的解決了這類問題
因?yàn)檫@個(gè)架構(gòu)要求你在寫 class 的時(shí)候需要寫相應(yīng)的 Config 文件,所以你要初始化很久以前的 Service 類的時(shí)候,前人都已經(jīng)寫好了 Config 文件,你直接在需要用的地方注入這個(gè) Service 就可以了。這大大增加了項(xiàng)目的可維護(hù)性且降低了開發(fā)難度。

最后我來總結(jié)下放上 IoC 的背誦版:
?? 面試官:講一下你對(duì) IoC (依賴注入) 的理解
?? 小牛肉:首先,IoC(Inverse of Control,控制反轉(zhuǎn))在其他語(yǔ)言中也有應(yīng)用,并非 Spring 特有,它是一種設(shè)計(jì)思想,基本概念就是將原本在程序中手動(dòng)創(chuàng)建對(duì)象的控制權(quán),交由 Spring 框架來管理。IoC 具體的實(shí)現(xiàn)方式是依賴注入。
簡(jiǎn)單的說之前我們?cè)诖a中創(chuàng)建一個(gè)對(duì)象是通過
new關(guān)鍵字,而使用了Spring之后,我們不在需要自己去new一個(gè)對(duì)象了,而是直接通過容器里面去取出來,再將其自動(dòng)注入到我們需要的對(duì)象之中,也就說創(chuàng)建對(duì)象的控制權(quán)不在我們程序員手上了,全部交由Spring進(jìn)行管理。交給 Spring 管理的也稱為 Bean,所有的 Bean 都被存儲(chǔ)在一個(gè) Map 集合中,這個(gè) Map 集合也稱為 IoC 容器。
將對(duì)象之間的相互依賴關(guān)系交給 IoC 容器來管理,并由 IoC 容器完成對(duì)象的注入。這樣可以很大程度上簡(jiǎn)化應(yīng)用的開發(fā),把應(yīng)用從復(fù)雜的依賴關(guān)系中解放出來。IoC 容器就像是一個(gè)工廠一樣,當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象的時(shí)候,只需要配置好配置文件/注解即可,完全不用考慮對(duì)象是如何被創(chuàng)建出來的。
比如說,在實(shí)際項(xiàng)目中一個(gè) Service 類可能有幾百甚至上千個(gè)類作為它的底層,假如我們需要實(shí)例化這個(gè) Service,你可能每次都要搞清這個(gè) Service 所有底層類的構(gòu)造函數(shù),這顯然過于繁瑣。如果利用 IoC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項(xiàng)目的可維護(hù)性且降低了開發(fā)難度。
心之所向,素履以往,我是小牛肉,小伙伴們下篇文章再見 ??
