背景:
假設(shè)有一個開源/第三方的軟件模塊ModuleA梳虽,我們要基于其上做特性的擴展垒手。我見過的較多的做法是二者的代碼實現(xiàn)揉在一起践磅,這樣會導(dǎo)致一個問題按傅。當(dāng)ModuleA做升級或者ModuleA替換成另一款ModuleB的時候,需要有大量的代碼做Merge適配服协。這樣帶來的一個比較大的問題是羞福,每次升級開源/三方軟件,極大概率會出現(xiàn)漏合錯合代碼的現(xiàn)象蚯涮。(我所經(jīng)歷的項目中,有因為漏合入一行代碼卖陵,引入概率性問題遭顶,攻關(guān)一個月之久)。因此我們需要有一種方法泪蔫,至少讓自研代碼和原始開源/三方軟件的代碼隔離開來棒旗,以插件式拼接的方式面對這種升級問題,而不是瑣碎的侵入式穿插修改撩荣。
案例:
下面我們就以一個例子為主線铣揉,講述一種在實戰(zhàn)中使用的案例。實實在在解決了上述痛點問題餐曹。
假設(shè)原生開源/三方軟件中有一個ModuleA逛拱,其中包含F(xiàn)unc1,和Func2兩個功能台猴,見圖1朽合。
假設(shè)此時,我們需要往ModuleA中增加一個功能FuncEx饱狂,以擴展ModuleA的邏輯從而滿足我們的需求曹步。其中調(diào)用關(guān)系為Func1->FuncEx->Func2。見圖2
圖2修改方法休讳,其實已經(jīng)進入了本文講的問題痛點范疇讲婚,自研代碼和原生代碼糾纏在一起。我們想辦法把它解耦出來俊柔。ModuleEx作為一個獨立的模塊提取出來筹麸,只包含F(xiàn)uncEx功能活合,放在獨立的文件和目錄中,見圖3
但此時出現(xiàn)了明顯的循環(huán)依賴竹捉。循環(huán)依賴倒不是關(guān)鍵問題芜辕,因為我上面說了,我們只是從代碼層面解耦開來块差,ModuleEx是無法脫離ModuleA單獨存在侵续,從本質(zhì)上來講,它只是附屬于ModuleA的一個片段憨闰。所以從編譯構(gòu)建和運行的角度状蜗,它們還是一個整體,循環(huán)依賴屬于組件模塊內(nèi)部的依賴鹉动。
我們之所以需要繼續(xù)改造它轧坎,是因為,它不夠穩(wěn)定泽示,設(shè)想一下缸血,如果由于升級ModuleA,F(xiàn)unc2有變化(名稱變更或者實現(xiàn)邏輯變化需要找另一個函數(shù)替代)械筛,必然導(dǎo)致FuncEx跟著變化捎泻。而正如架構(gòu)整潔之道中講的,好的架構(gòu)需要保護自己的核心域不受影響埋哟。因此解決方法也是使用Bob大叔提供的方法笆豁,增加接口實現(xiàn)依賴倒置,從而也解決了循環(huán)依賴的問題赤赊。這樣從架構(gòu)邊界上看闯狱,就變成了開源/三方依賴自研。見圖4
但實際上抛计,這里還有一個問題哄孤,我們沒有辦法撼動開源/三方的ModuleA實現(xiàn)我們的接口Func2Inf。我們只能自己來做這件事情爷辱,畢竟受益的是我們自己录豺。因此我們增加了ModuleAAdapter,來實現(xiàn)接口Func2Inf饭弓。見圖5
從圖5看双饥,好像對ModuleA的依賴又變成了循環(huán)的,但其實不然弟断。我們要把ModuleAAdapter看作ModuleA的一部分咏花,則ModuleA和ModuleEx之間仍然是單向依賴。只不過ModuleAAdapter的實現(xiàn)由ModuleA的使用方來進行。ModuleAAdapter會隨著ModuleA的變化而變化昏翰,但是也僅限于ModuleAAdapter的變化苍匆,我們用維護ModuleAAdapter這一層的代價,保護了ModuleEx的穩(wěn)定性棚菊。
我們繼續(xù)演化浸踩,假設(shè)需要用ModuleB替代ModuleA,則ModuleB可能沒有辦法直接調(diào)用ModuleEx中的FuncEx统求,需要額外增加一點修改才能供ModuleB使用检碗。這是一種典型的需要增加適配層的場景。因此码邻,增加一層對ModuleEx的適配層折剃。見圖6
至此,整個問題的解決方案已經(jīng)講述完畢像屋,最后怕犁,我們需要用不同的層次來看待這張圖,從代碼維護者的角度劃分和從依賴關(guān)系的角度劃分己莺,會存在兩個架構(gòu)邊界奏甫,見圖7。很多人可能糾結(jié)于維護邊界上的循環(huán)依賴而裹足不前凌受,而本案例中扶檐,承認(rèn)這種循環(huán)依賴的無害性,完美地解決了面臨的問題胁艰。
有人會追問,這種場景下到底能不能設(shè)計成明確的單向依賴智蝠?我們通常意義上說的單向依賴腾么,一般是組件間的依賴,它們之間應(yīng)該有較少的接口依賴杈湾,往往比較容易做成易變的依賴穩(wěn)定的組件解虱。即便有少量的循環(huán)依賴,也可以通過插入接口等方式實現(xiàn)依賴導(dǎo)致漆撞。
本文中的場景殴泰,更多的是同一組件內(nèi)的模塊間的依賴,理論上講浮驳,如果一個組件屬于同一團隊開發(fā)悍汛,內(nèi)部模塊間的依賴,應(yīng)該屬于正常依賴至会。但是由于本文的特殊場景离咐,開源三方和自研特性的開發(fā)者不屬于同一個團隊,所以通過增加接口和適配層,讓變化局限在適配層這部分宵蛀,從而最大限度保護核心業(yè)務(wù)邏輯不受影響昆著。從編譯構(gòu)建的角度看,它們其實仍然是一個整體术陶,任何一塊都無法單獨構(gòu)建運行凑懂,其實歸根結(jié)底還是存在依賴,只不過從開發(fā)的視角梧宫,代碼隔離開來了接谨。但是把代碼隔離開來,正是本文場景中最迫切要解決的訴求祟敛。