<meta charset="utf-8">
作者:Sevenvidia
鏈接:https://www.zhihu.com/question/23277575/answer/169698662
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)绞惦,非商業(yè)轉(zhuǎn)載請注明出處逼纸。
要了解控制反轉(zhuǎn)( Inversion of Control ), 我覺得有必要先了解軟件設(shè)計(jì)的一個(gè)重要思想:依賴倒置原則(Dependency Inversion Principle )。
什么是依賴倒置原則济蝉?假設(shè)我們設(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ù)市場需求的變動(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)系就倒置過來了:輪子依賴底盤, 底盤依賴車身杨何, 車身依賴汽車酱塔。
<noscript>這時(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)全身”的情況贸诚。
<noscript>控制反轉(zhuǎn)(Inversion of Control) 就是依賴倒置原則的一種代碼設(shè)計(jì)的思路祷愉。具體采用的方法就是所謂的依賴注入(Dependency Injection)。其實(shí)這些概念初次接觸都會(huì)感到云里霧里的赦颇。說穿了二鳄,這幾種概念的關(guān)系大概如下:
<noscript>為了理解這幾個(gè)概念,我們還是用上面汽車的例子媒怯。只不過這次換成代碼订讼。我們先定義四個(gè)Class,車扇苞,車身欺殿,底盤,輪胎鳖敷。然后初始化這輛車脖苏,最后跑這輛車。代碼結(jié)構(gòu)如下:
<noscript>這樣定踱,就相當(dāng)于上面第一個(gè)例子棍潘,上層建筑依賴下層建筑——每一個(gè)類的構(gòu)造函數(shù)都直接調(diào)用了底層代碼的構(gòu)造函數(shù)。假設(shè)我們需要改動(dòng)一下輪胎(Tire)類崖媚,把它的尺寸變成動(dòng)態(tài)的亦歉,而不是一直都是30。我們需要這樣改:
<noscript>由于我們修改了輪胎的定義畅哑,為了讓整個(gè)程序正常運(yùn)行肴楷,我們需要做以下改動(dòng):
<noscript>由此我們可以看到,僅僅是為了修改輪胎的構(gòu)造函數(shù)荠呐,這種設(shè)計(jì)卻需要修改整個(gè)上層所有類的構(gòu)造函數(shù)赛蔫!在軟件工程中,這樣的設(shè)計(jì)幾乎是不可維護(hù)的——在實(shí)際工程項(xiàng)目中泥张,有的類可能會(huì)是幾千個(gè)類的底層呵恢,如果每次修改這個(gè)類,我們都要修改所有以它作為依賴的類圾结,那軟件的維護(hù)成本就太高了瑰剃。
所以我們需要進(jìn)行控制反轉(zhuǎn)(IoC),及上層控制下層筝野,而不是下層控制著上層晌姚。我們用依賴注入(Dependency Injection)這種方式來實(shí)現(xiàn)控制反轉(zhuǎn)粤剧。所謂依賴注入,就是把底層類作為參數(shù)傳入上層類挥唠,實(shí)現(xiàn)上層類對下層類的“控制”抵恋。這里我們用構(gòu)造方法傳遞的依賴注入方式重新寫車類的定義:
<noscript>這里我們再把輪胎尺寸變成動(dòng)態(tài)的,同樣為了讓整個(gè)系統(tǒng)順利運(yùn)行宝磨,我們需要做如下修改:
<noscript>看到?jīng)]弧关?這里我只需要修改輪胎類就行了,不用修改其他任何上層類唤锉。這顯然是更容易維護(hù)的代碼世囊。不僅如此,在實(shí)際的工程中窿祥,這種設(shè)計(jì)模式還有利于不同組的協(xié)同合作和單元測試:比如開發(fā)這四個(gè)類的分別是四個(gè)不同的組株憾,那么只要定義好了接口,四個(gè)不同的組可以同時(shí)進(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)髓帽。
看到這里你應(yīng)該能理解什么控制反轉(zhuǎn)和依賴注入了菠赚。那什么是控制反轉(zhuǎn)容器(IoC Container)呢?其實(shí)上面的例子中郑藏,對車類進(jìn)行初始化的那段代碼發(fā)生的地方,就是控制反轉(zhuǎn)容器瘩欺。
顯然你也應(yīng)該觀察到了必盖,因?yàn)椴捎昧艘蕾囎⑷耄诔跏蓟倪^程中就不可避免的會(huì)寫大量的new俱饿。這里IoC容器就解決了這個(gè)問題歌粥。這個(gè)容器可以自動(dòng)對你的代碼進(jìn)行初始化,你只需要維護(hù)一個(gè)Configuration(可以是xml可以是一段代碼)拍埠,而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼失驶。這是引入IoC Container的第一個(gè)好處。
IoC Container的第二個(gè)好處是:我們在創(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è)工廠:
</noscript>
</figure>
我們就像是工廠的客戶铆惑。我們只需要向工廠請求一個(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ā)難度。
<noscript></noscript>
</figure>
這里只是很粗略的講了一下我自己對IoC和DI的理解谒养。主要的目的是在于最大限度避免晦澀難懂的專業(yè)詞匯挺狰,用盡量簡潔,通俗买窟,直觀的例子來解釋這些概念丰泊。如果讓大家能有一個(gè)類似“哦!原來就是這么個(gè)玩意嘛始绍!”的印象瞳购,我覺得就OK了。想要深入了解的話亏推,可以上網(wǎng)查閱一些更權(quán)威的資料学赛。這里推薦一下 Dependency injection 和 Inversion of Control Containers and the Dependency Injection pattern 這兩篇文章,講的很好很詳細(xì)吞杭。