Spring IoC有什麼好處

原文:https://www.zhihu.com/question/23277575


筆記:

DI和IOC其實是一個思想,她們的的好處是:如果依賴的類修改了,比如修改了構造函數,如果沒有依賴注入,則需要修改依賴對象調用着,如果依賴注入則不需要。

spring IOC的好處是,對象的構建如果依賴非常多的對象,且層次很深,外層在構造對象時很麻煩且不一定知道如何構建這麼多層次的對象。 IOC 幫我們管理對象的創建,只需要在配置文件裏指定如何構建,每一個對象的配置文件都在類編寫的時候指定了,所以最外層對象不需要關心深層次對象如何創建的,前人都寫好了。


作者:Sevenvidia
鏈接:https://www.zhihu.com/question/23277575/answer/169698662
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

要了解控制反轉( Inversion of Control ), 我覺得有必要先了解軟件設計的一個重要思想:依賴倒置原則(Dependency Inversion Principle )

<img src="https://pic1.zhimg.com/50/v2-d53c75e91d959acbb0d95a835212ada5_hd.jpg" data-caption="" data-rawwidth="600" data-rawheight="61" class="origin_image zh-lightbox-thumb" width="600" data-original="https://pic1.zhimg.com/v2-d53c75e91d959acbb0d95a835212ada5_r.jpg">

什麼是依賴倒置原則?假設我們設計一輛汽車:先設計輪子,然後根據輪子大小設計底盤,接着根據底盤設計車身,最後根據車身設計好整個汽車。這裏就出現了一個“依賴”關係:汽車依賴車身,車身依賴底盤,底盤依賴輪子。

<img src="https://pic4.zhimg.com/50/v2-c68248bb5d9b4d64d22600571e996446_hd.jpg" data-caption="" data-rawwidth="1562" data-rawheight="186" class="origin_image zh-lightbox-thumb" width="1562" data-original="https://pic4.zhimg.com/v2-c68248bb5d9b4d64d22600571e996446_r.jpg">

這樣的設計看起來沒問題,但是可維護性卻很低。假設設計完工之後,上司卻突然說根據市場需求的變動,要我們把車子的輪子設計都改大一碼。這下我們就蛋疼了:因爲我們是根據輪子的尺寸設計的底盤,輪子的尺寸一改,底盤的設計就得修改;同樣因爲我們是根據底盤設計的車身,那麼車身也得改,同理汽車設計也得改——整個設計幾乎都得改!

我們現在換一種思路。我們先設計汽車的大概樣子,然後根據汽車的樣子來設計車身,根據車身來設計底盤,最後根據底盤來設計輪子。這時候,依賴關係就倒置過來了:輪子依賴底盤, 底盤依賴車身, 車身依賴汽車。

<img src="https://pic1.zhimg.com/50/v2-e64bf72c5c04412f626b21753aa9e1a1_hd.jpg" data-caption="" data-rawwidth="1504" data-rawheight="190" class="origin_image zh-lightbox-thumb" width="1504" data-original="https://pic1.zhimg.com/v2-e64bf72c5c04412f626b21753aa9e1a1_r.jpg">

這時候,上司再說要改動輪子的設計,我們就只需要改動輪子的設計,而不需要動底盤,車身,汽車的設計了。

這就是依賴倒置原則——把原本的高層建築依賴底層建築“倒置”過來,變成底層建築依賴高層建築。高層建築決定需要什麼,底層去實現這樣的需求,但是高層並不用管底層是怎麼實現的。這樣就不會出現前面的“牽一髮動全身”的情況。

<img src="https://pic1.zhimg.com/50/v2-d53c75e91d959acbb0d95a835212ada5_hd.jpg" data-caption="" data-rawwidth="600" data-rawheight="61" class="origin_image zh-lightbox-thumb" width="600" data-original="https://pic1.zhimg.com/v2-d53c75e91d959acbb0d95a835212ada5_r.jpg">

控制反轉(Inversion of Control) 就是依賴倒置原則的一種代碼設計的思路。具體採用的方法就是所謂的依賴注入(Dependency Injection)。其實這些概念初次接觸都會感到雲裏霧裏的。說穿了,這幾種概念的關係大概如下:

<img src="https://pic1.zhimg.com/50/v2-ee924f8693cff51785ad6637ac5b21c1_hd.jpg" data-caption="" data-rawwidth="1398" data-rawheight="630" class="origin_image zh-lightbox-thumb" width="1398" data-original="https://pic1.zhimg.com/v2-ee924f8693cff51785ad6637ac5b21c1_r.jpg">

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

<img src="https://pic3.zhimg.com/50/v2-8ec294de7d0f9013788e3fb5c76069ef_hd.jpg" data-caption="" data-rawwidth="512" data-rawheight="717" class="origin_image zh-lightbox-thumb" width="512" data-original="https://pic3.zhimg.com/v2-8ec294de7d0f9013788e3fb5c76069ef_r.jpg">

這樣,就相當於上面第一個例子,上層建築依賴下層建築——每一個類的構造函數都直接調用了底層代碼的構造函數。假設我們需要改動一下輪胎(Tire)類,把它的尺寸變成動態的,而不是一直都是30。我們需要這樣改:

<img src="https://pic4.zhimg.com/50/v2-64e8b19eeb70d9cf87c27fe4c5c0fc81_hd.jpg" data-caption="" data-rawwidth="534" data-rawheight="154" class="origin_image zh-lightbox-thumb" width="534" data-original="https://pic4.zhimg.com/v2-64e8b19eeb70d9cf87c27fe4c5c0fc81_r.jpg">

由於我們修改了輪胎的定義,爲了讓整個程序正常運行,我們需要做以下改動:

<img src="https://pic2.zhimg.com/50/v2-82e0c12a1b26f7979ed9241e169affda_hd.jpg" data-caption="" data-rawwidth="1186" data-rawheight="1452" class="origin_image zh-lightbox-thumb" width="1186" data-original="https://pic2.zhimg.com/v2-82e0c12a1b26f7979ed9241e169affda_r.jpg">

由此我們可以看到,僅僅是爲了修改輪胎的構造函數,這種設計卻需要修改整個上層所有類的構造函數!在軟件工程中,這樣的設計幾乎是不可維護的——在實際工程項目中,有的類可能會是幾千個類的底層,如果每次修改這個類,我們都要修改所有以它作爲依賴的類,那軟件的維護成本就太高了。

所以我們需要進行控制反轉(IoC),及上層控制下層,而不是下層控制着上層。我們用依賴注入(Dependency Injection)這種方式來實現控制反轉。所謂依賴注入,就是把底層類作爲參數傳入上層類,實現上層類對下層類的“控制”。這裏我們用構造方法傳遞的依賴注入方式重新寫車類的定義:

<img src="https://pic1.zhimg.com/50/v2-c920a0540ce0651003a5326f6ef9891d_hd.jpg" data-caption="" data-rawwidth="1338" data-rawheight="1424" class="origin_image zh-lightbox-thumb" width="1338" data-original="https://pic1.zhimg.com/v2-c920a0540ce0651003a5326f6ef9891d_r.jpg">

這裏我們再把輪胎尺寸變成動態的,同樣爲了讓整個系統順利運行,我們需要做如下修改:

<img src="https://pic4.zhimg.com/50/v2-99ad2cd809fcb86dd791ff7f65fb1779_hd.jpg" data-caption="" data-rawwidth="1344" data-rawheight="1424" class="origin_image zh-lightbox-thumb" width="1344" data-original="https://pic4.zhimg.com/v2-99ad2cd809fcb86dd791ff7f65fb1779_r.jpg">

看到沒?這裏我只需要修改輪胎類就行了,不用修改其他任何上層類。這顯然是更容易維護的代碼。不僅如此,在實際的工程中,這種設計模式還有利於不同組的協同合作和單元測試:比如開發這四個類的分別是四個不同的組,那麼只要定義好了接口,四個不同的組可以同時進行開發而不相互受限制;而對於單元測試,如果我們要寫Car類的單元測試,就只需要Mock一下Framework類傳入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再來構造Car。

這裏我們是採用的構造函數傳入的方式進行的依賴注入。其實還有另外兩種方法:Setter傳遞接口傳遞。這裏就不多講了,核心思路都是一樣的,都是爲了實現控制反轉

<img src="https://pic1.zhimg.com/50/v2-861683acac47577c81f2b7493dd05649_hd.jpg" data-caption="" data-rawwidth="924" data-rawheight="298" class="origin_image zh-lightbox-thumb" width="924" data-original="https://pic1.zhimg.com/v2-861683acac47577c81f2b7493dd05649_r.jpg">


<img src="https://pic1.zhimg.com/50/v2-d53c75e91d959acbb0d95a835212ada5_hd.jpg" data-caption="" data-rawwidth="600" data-rawheight="61" class="origin_image zh-lightbox-thumb" width="600" data-original="https://pic1.zhimg.com/v2-d53c75e91d959acbb0d95a835212ada5_r.jpg">

看到這裏你應該能理解什麼控制反轉和依賴注入了。那什麼是控制反轉容器(IoC Container)呢?其實上面的例子中,對車類進行初始化的那段代碼發生的地方,就是控制反轉容器。

<img src="https://pic4.zhimg.com/50/v2-c845802f9187953ed576e0555f76da42_hd.jpg" data-caption="" data-rawwidth="1422" data-rawheight="628" class="origin_image zh-lightbox-thumb" width="1422" data-original="https://pic4.zhimg.com/v2-c845802f9187953ed576e0555f76da42_r.jpg">

顯然你也應該觀察到了,因爲採用了依賴注入,在初始化的過程中就不可避免的會寫大量的new。這裏IoC容器就解決了這個問題。這個容器可以自動對你的代碼進行初始化,你只需要維護一個Configuration(可以是xml可以是一段代碼),而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼。這是引入IoC Container的第一個好處。

IoC Container的第二個好處是:我們在創建實例的時候不需要了解其中的細節。在上面的例子中,我們自己手動創建一個車instance時候,是從底層往上層new的:

<img src="https://pic1.zhimg.com/50/v2-555b2be7d76e78511a6d6fed3304927f_hd.jpg" data-caption="" data-rawwidth="2430" data-rawheight="168" class="origin_image zh-lightbox-thumb" width="2430" data-original="https://pic1.zhimg.com/v2-555b2be7d76e78511a6d6fed3304927f_r.jpg">

這個過程中,我們需要了解整個Car/Framework/Bottom/Tire類構造函數是怎麼定義的,才能一步一步new/注入。

而IoC Container在進行這個工作的時候是反過來的,它先從最上層開始往下找依賴關係,到達最底層之後再往上一步一步new(有點像深度優先遍歷):

<img src="https://pic3.zhimg.com/50/v2-24a96669241e81439c636e83976ba152_hd.jpg" data-caption="" data-rawwidth="2522" data-rawheight="354" class="origin_image zh-lightbox-thumb" width="2522" data-original="https://pic3.zhimg.com/v2-24a96669241e81439c636e83976ba152_r.jpg">

這裏IoC Container可以直接隱藏具體的創建實例的細節,在我們來看它就像一個工廠:

<img src="https://pic6.zhimg.com/50/v2-5ca61395f37cef73c7bbe7808f9ea219_hd.jpg" data-caption="" data-rawwidth="2448" data-rawheight="524" class="origin_image zh-lightbox-thumb" width="2448" data-original="https://pic6.zhimg.com/v2-5ca61395f37cef73c7bbe7808f9ea219_r.jpg">

我們就像是工廠的客戶。我們只需要向工廠請求一個Car實例,然後它就給我們按照Config創建了一個Car實例。我們完全不用管這個Car實例是怎麼一步一步被創建出來。

實際項目中,有的Service Class可能是十年前寫的,有幾百個類作爲它的底層。假設我們新寫的一個API需要實例化這個Service,我們總不可能回頭去搞清楚這幾百個類的構造函數吧?IoC Container的這個特性就很完美的解決了這類問題——因爲這個架構要求你在寫class的時候需要寫相應的Config文件,所以你要初始化很久以前的Service類的時候,前人都已經寫好了Config文件,你直接在需要用的地方注入這個Service就可以了。這大大增加了項目的可維護性且降低了開發難度。

<img src="https://pic1.zhimg.com/50/v2-d53c75e91d959acbb0d95a835212ada5_hd.jpg" data-caption="" data-rawwidth="600" data-rawheight="61" class="origin_image zh-lightbox-thumb" width="600" data-original="https://pic1.zhimg.com/v2-d53c75e91d959acbb0d95a835212ada5_r.jpg">

這裏只是很粗略的講了一下我自己對IoC和DI的理解。主要的目的是在於最大限度避免晦澀難懂的專業詞彙,用盡量簡潔,通俗,直觀的例子來解釋這些概念。如果讓大家能有一個類似“哦!原來就是這麼個玩意嘛!”的印象,我覺得就OK了。想要深入瞭解的話,可以上網查閱一些更權威的資料。這裏推薦一下 Dependency injection Inversion of Control Containers and the Dependency Injection pattern 這兩篇文章,講的很好很詳細。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章