沒項目經(jīng)歷的安醬,連低耦合高內(nèi)聚都不懂...
說完,視頻聊天欄那頭的面試官,匆匆下了線。只留下怔在座位上一臉懵逼的安醬。
這場面試下來,安醬其實自我感覺還不錯。面試官看起來比較親和,問的問題也很常規(guī),都是些關(guān)于TCP/UDP的區(qū)別,線程進程通信方式這類的問題。
安醬對這種熱門的面試題早已經(jīng)滾瓜爛熟,手邊擺著的程序員面試寶典也已經(jīng)有些破爛不堪,顯然是翻閱了許多遍。但是面試官最后一句話卻讓他有些心底發(fā)寒。
總的來說,面試官問的問題并不多,主要集中在計算機網(wǎng)絡(luò)和操作系統(tǒng)基本原理。但最后要結(jié)束的時候,面試官話鋒一轉(zhuǎn),沒有繼續(xù)在剛剛守護進程的問題上深入,而是突然問了一個關(guān)于編程思想的問題。
「你平時代碼寫得多不多?知道為什么要低耦合高內(nèi)聚嗎?」這個問題開放程度比較高,按道理來說并不難。
可是對于安醬來說,卻有些不太好回答。因為他并不是純正的CS科班出身,同時在學(xué)校期間也沒有接觸過什么大項目。所謂作為「負責(zé)人」的項目經(jīng)歷實際上都不過是拼拼湊湊而成的小軟件或者小系統(tǒng)。
「emmm...我平時代碼寫的不是特別多。我感覺低耦合就是讓代碼之間的聯(lián)系小一點吧,然后高聚合可能就是把類似的功能寫在一個函數(shù)里...大概就這個樣子。」安醬強行回答了一波,他感覺這種東西太難描述了,腦子里模模糊糊的。
「沒了嗎?那你在做項目的時候有沒有涉及到模塊化或者組件化這些東西呢?」面試官依然保持著他那從始至終的微笑。
「嗯可能是項目比較小,應(yīng)該都沒怎么接觸過...」瞬間沒了底氣。
「沒關(guān)系哈,那感謝你的時間,回去再了解下低耦合高內(nèi)聚吧。」面試官笑著點了下頭,退出了聊天室。
這其實也是我的一段真實面試經(jīng)歷。剛開始出去準(zhǔn)備求職面試的時候,由于缺乏完整全面的項目經(jīng)歷,連代碼的高內(nèi)聚低耦合都說不太清楚。而如今開始參與大型的項目開發(fā)時,往往離不開對這六個字的思考和實踐。
高內(nèi)聚,低耦合。想必寫過代碼的人都聽說過這條原則,但是真要把六個字解釋清楚卻并不簡單。因為這六個字的思想太濃縮了,包含了設(shè)計模式的幾大原則,體現(xiàn)了面對對象的開發(fā)思想,甚至能提供軟件系統(tǒng)架構(gòu)演化的方案和思路。
所以這回咱們就試著深入淺出的聊聊為啥咱們的代碼要高內(nèi)聚,低耦合。
1 內(nèi)聚
首先還是概念性的東西。
內(nèi)聚:從功能角度來度量模塊內(nèi)的聯(lián)系。
耦合:各模塊間相互聯(lián)系緊密程度的一種度量,取決于模塊間接口的復(fù)雜性、調(diào)用的方式及傳遞的信息。
簡單來說,內(nèi)聚就是指某一段程序所能做的事情的關(guān)聯(lián)性。如果這段程序所完成的功能都類似,比方說都在完成文件操作,或者網(wǎng)絡(luò)請求等,那么這一段代碼就會被認為是內(nèi)聚性高的。而如果這段程序只是各種操作的混合,一會在處理文件,一會又在刷新界面,這樣的程序就會被認為是內(nèi)聚性低的。
內(nèi)聚性最實在的要求就是每個模塊盡可能獨立完成自己的功能,不依賴于模塊外部的代碼。這里的模塊實際上只是程序的一個粒度劃分,可以是一個函數(shù),一個類,也可以是一個包,一個空間。一般而言,粒度越大的模塊所要求的內(nèi)舉性就越高。
內(nèi)聚性根據(jù)模塊內(nèi)部不同的聯(lián)系方式,還可以分為好幾種,包括邏輯內(nèi)聚、時間內(nèi)聚、過程內(nèi)聚等。不同的內(nèi)聚方式實際上就是看代碼是以什么形式組織起來的。比方說邏輯內(nèi)聚,就是依據(jù)判斷條件的不同從而執(zhí)行不同的邏輯;時間內(nèi)聚就是按照程序執(zhí)行的時間順序來組織不同部分的代碼。
所以代碼的內(nèi)聚性是為了保證每個模塊的功能單一,從而能夠充當(dāng)最基本的組件被其它程序調(diào)用。這樣的代碼結(jié)構(gòu)更利于維護和架構(gòu)升級。
2 耦合
耦合是一個很常見的概念,在很多領(lǐng)域都會用到。而在一些老項目的開發(fā)重構(gòu)中,經(jīng)常需要對程序做一些解耦的工作。所謂解耦就是降低程序整體的耦合性,提高不同模塊之間的內(nèi)聚性。
耦合是一件非常容易而沒有成本的事情。耦合意味著混亂,意味著關(guān)系錯綜復(fù)雜,牽一發(fā)而動全身。同樣耦合根據(jù)模塊間的聯(lián)系方式的不同,也可以分為好幾種。這里挑了幾組耦合方式來嘗試著理解一下。
數(shù)據(jù)耦合: 調(diào)用模塊和被調(diào)用模塊之間只傳遞簡單的數(shù)據(jù)項參數(shù)。相當(dāng)于高級語言中的值傳遞。
控制耦合: 模塊之間傳遞的不是數(shù)據(jù)信息,而是控制信息例如標(biāo)志、開關(guān)量等,一個模塊控制了另一個模塊的功能。
外部耦合: 一組模塊都訪問同一全局簡單變量,而且不通過參數(shù)表傳遞該全局變量的信息。
公共耦合: 一組模塊都訪問同一個全局數(shù)據(jù)結(jié)構(gòu),則稱之為公共耦合。公共數(shù)據(jù)環(huán)境可以是全局數(shù)據(jù)結(jié)構(gòu)、共享的通信區(qū)、內(nèi)存的公共覆蓋區(qū)等。如果模塊只是向公共數(shù)據(jù)環(huán)境輸入數(shù)據(jù),或是只從公共數(shù)據(jù)環(huán)境取出數(shù)據(jù),這屬于比較松散的公共耦合;如果模塊既向公共數(shù)據(jù)環(huán)境輸入數(shù)據(jù)又從公共數(shù)據(jù)環(huán)境取出數(shù)據(jù),這屬于較緊密的公共耦合。
對于耦合實際上是很好理解的。假如把每個模塊比喻成一個齒輪,那么高耦合度的系統(tǒng)就像下圖所示。每個齒輪之間相互卡扣,一個齒輪的運動會帶動其它多個齒輪同時運轉(zhuǎn)。同時當(dāng)多個齒輪獨立運動時,還會因為相互卡死而導(dǎo)致系統(tǒng)被鎖住。
在具體的項目中,這樣的情況并不少見。最簡單的場景就是,當(dāng)一個對象依賴于另一個對象的實現(xiàn),那就相當(dāng)于建立了一層耦合。當(dāng)這樣的情況增多,整個系統(tǒng)中的依賴關(guān)系就顯得臃腫而復(fù)雜。多個對象之間相互依賴,甚至還會有多重依賴循環(huán)依賴的情況。這種情況在大型軟件系統(tǒng)的迭代中是非常致命的。
3 解耦
既然如此,那么在對軟件架構(gòu)進行升級優(yōu)化時,就不可避免的需要對系統(tǒng)進行解耦。解耦的方式有很多,可以根據(jù)不同語言的特性或者框架本身提供的方案進行。但這些方案本質(zhì)上的目的都是一個,減少齒輪間的依賴。
減少依賴很容易,無非就是讓齒輪之間的距離遠一點,你碰不著我,我也摸不到你。這在實際的系統(tǒng)里可表現(xiàn)為每一個對象不直接使用別的對象里的資源,包括方法屬性等。
齒輪分開是分開了,但是很明顯這樣的系統(tǒng)是無法運轉(zhuǎn)的。那么該如何去建立各個齒輪之間的聯(lián)系并傳遞動力呢?這里有一種比較通用的方式。
既然每個齒輪不能直接相聯(lián),那么給它們加個中轉(zhuǎn)站不就行了。通過一個別的東西來對其它對不同齒輪之間的消息或者運動進行傳遞。這是不是就像是集中式的電話轉(zhuǎn)接線路,所以說分布式也并不是啥情況都好用的。
通常來說,這樣的中轉(zhuǎn)站可以是作為一種接口的服務(wù)(moduleService),或者說是一種管理器(Manager)。通過這樣的中介來進行不同模塊之間資源的請求和返回。
一句話而言,這種方案就是通過一個第三方在對不同模塊/組件間的對象進行解耦。所有對象的控制權(quán)交由這個第三方執(zhí)行,通過中間人返回某個對象所需要的外部資源(包括對象、資源、常量數(shù)據(jù))。
看到這是不是有種似曾相識的感覺。沒錯,這就是常說的IoC容器。
4 IoC 容器
先裝個逼,丟兩個個名詞。
IoC容器:Inversion of Control,“控制反轉(zhuǎn)”。
DI:Dependency Injection,即“依賴注入”。
IoC不是一種技術(shù),只是一種思想,一個重要的面向?qū)ο缶幊痰姆▌t,它能指導(dǎo)我們?nèi)绾卧O(shè)計出松耦合、更優(yōu)良的程序。傳統(tǒng)應(yīng)用程序都是由我們在類內(nèi)部主動創(chuàng)建依賴對象,從而導(dǎo)致類與類之間高耦合,難于測試;有了IoC容器后,把創(chuàng)建和查找依賴對象的控制權(quán)交給了容器,由容器進行注入組合對象,所以對象與對象之間是松散耦合,這樣也方便測試,利于功能復(fù)用,更重要的是使得程序的整個體系結(jié)構(gòu)變得非常靈活。(知乎大佬的解釋)
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發(fā)生了“主從換位”的變化。應(yīng)用程序原本是老大,要獲取什么資源都是主動出擊,但是在IoC/DI思想中,應(yīng)用程序就變成被動的了,被動的等待IoC容器來創(chuàng)建并注入它所需要的資源了。
依賴注入是在IoC的基礎(chǔ)更為具體的一種描述,明確描述了「被注入對象依賴IoC容器配置依賴對象」。聽起來有些深奧,不過概念不重要。
所以,IOC簡單來說就是把復(fù)雜系統(tǒng)分解成相互合作的對象,這些對象類通過封裝以后,內(nèi)部實現(xiàn)對外部是透明的,從而降低了解決問題的復(fù)雜度,而且可以靈活地被重用和擴展。
低耦合高內(nèi)聚是在編程中是極其重要的思想,從局部決定著系統(tǒng)整體的發(fā)展和演進。說實話我也沒怎么摸清楚,只不過是由于最近剛?cè)肼殐蓚€月,老大讓進行一個小范圍的重構(gòu),所以寫篇文章記錄一下子。水平有限,keep moving...
作者簡介:我是安醬,一個稀里糊涂地進了大廠的業(yè)余碼農(nóng)。分享全棧技術(shù),目標(biāo)架構(gòu)師。關(guān)注我,一起向技術(shù)大牛進階!
本文轉(zhuǎn)載自微信公眾號「 業(yè)余碼農(nóng)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 業(yè)余碼農(nóng)公眾號。