組件聚合
組件的定義
組件是軟件部署的最小單元,是整個軟件系統(tǒng)在部署過程中可以獨立完成部署的最小實體。比如,對于Java應用程序而言,Jar包就是組件;Ruby中的組件則是Gem文件;Python中的Egg或Wheel文件以及.Net下的DLL文件。
上回我們說到,編程范式的本質(zhì)是約束。子過程、類或函數(shù)是我們編程過程中的基本元素,所以說編程范式是程序的基礎(chǔ)構(gòu)件。如果將這些基本構(gòu)件比作建筑里的泥沙石,那么程序中的組件就可以類比成磚頭。磚頭的工藝注重材料配比,組件也是如此,恰如其分的基礎(chǔ)構(gòu)件配比是組件穩(wěn)定的基礎(chǔ)。組件的內(nèi)容配比較難定量,但是在實踐上,仍然受到指導原則的約束。
軟件工程中的約束三角
在軟件工程中,我們會看到很多約束條件都能由三角形的方式體現(xiàn)出來。這是因為三角形除了具有穩(wěn)定的特性以外,還能體現(xiàn)出一種張力。

比如在敏捷項目管理中,我們常會聽到時間,資源和成本的約束三角;在分布式計算中,著名的CAP(一致性,可用性和分區(qū)容錯性)原理也是如此;還有區(qū)塊鏈中的不可能三角(性能,安全和去中心化)。這些三角都在反映一種現(xiàn)實中的約束——因為不能全部同時滿足,所以需要權(quán)衡。
組件聚合張力圖
組件的內(nèi)容配比,最終反映在組件的實踐上就是基本構(gòu)件的拆與合。鮑勃大叔給出了三個拆合的指導原則:REP(復用/發(fā)布等同原則),CCP(共同閉包原則)和CRP(共同復用原則)。

- REP(復用/發(fā)布等同原則):軟件復用的最小粒度應該等同于其發(fā)布的最小粒度(注:只有那些通過版本追蹤系統(tǒng)發(fā)布的組件才能被高效地復用)
- CCP(共同閉包原則):將同時修改,目的相同的類放到同一個組件;不會同時修改,目的不同的類放到不同的組件
- CRP(共同復用原則):不要強迫一個組件的用戶依賴他們不需要的東西
這些原則乍看上去是全新的理念,細細品來又好像“新瓶裝舊酒”的老把戲。CCP不就是SRP(單一職能原則)?CRP不就是ISP(接口隔離原則)?REP,等等,這個原則不言自明地像個公理呀!難怪有些架構(gòu)師朋友說,鮑勃大叔老了,又拿著SOLID那一套概念出來忽悠騙錢。
其實,不妨換個思路想想,通常當談論SOLID、高內(nèi)聚低耦合、穩(wěn)定依賴、穩(wěn)定抽象系列原則的時候,我們是處于軟件系統(tǒng)生命周期的哪一環(huán)?不出意外,大家都是從編寫源代碼,即開發(fā)(Development)的角度出發(fā)的。但是,我們又清晰地了解,軟件系統(tǒng)的生命周期其實還包含除開發(fā)之外的部署、發(fā)布,運行和維護環(huán)節(jié)。那么問題來了,在這些環(huán)節(jié)里,哪些指導原則是適用的呢?
在跳脫了開發(fā)的思維桎梏之后,我們通過兩種手段分析下這三條原則。
分開看
REP原則闡述了一個簡單的道理:軟件復用是基本要求。在追求軟件復用的過程中,逐步形成了標準的發(fā)布流程,如:版本號(語義化版本),發(fā)布時間,變更內(nèi)容等。這要求組件中所包含的模塊和類都必須同時可發(fā)布,而可發(fā)布的深層含義既是對用戶的承諾,也是對作者的約束。組件是否向后兼容?是否包含破壞性的變更?升級的注意事項?
CCP原則是指盡量把變更頻率相同的模塊和類放到同一個組件當中。這樣做的好處是,當相關(guān)功能更新時,我們可以把源代碼的變更局限在某一個組件當中,而不需要橫跨多個組件,從而減少了部署,驗證和發(fā)布的次數(shù)。概括來說,這是局部化影響的優(yōu)勢。CCP和OCP(開閉原則)中強調(diào)的“閉包”也有關(guān)聯(lián),所謂封裝可變因素就是形成閉包的過程,CCP要求將同一時間變更的點聚合起來,達到閉包的效果。
CRP原則是說組件和組件之間的依賴應該達成一種默契——如果不需要完全使用某個組件中所有的模塊和類,那么就不要依賴它。這看上去不太可能,但是有一點意義,它指導我們:不是緊密相連的模塊和類不應該被放到同一個組件里。因為我們知道一旦某個組件變更升級之后,依賴它的組件往往也會被動的變更升級,即便是和自己那些無關(guān)的變更也是如此。而每次變更都意味著重新編譯,部署驗證和發(fā)布。
合起看
REP原則說明軟件復用是基礎(chǔ),復用是通過發(fā)布流程規(guī)范的。在復用和發(fā)布的上下文中,CCP原則為了便于后期維護,需要盡可能地將變更頻率相同的模塊和類放到相同的復用單元——組件中;CRP原則為了避免頻繁發(fā)布,應該將每個組件分割的足夠小,減少無關(guān)變更導致依賴鏈條的連鎖發(fā)布反應。
如果我們只兼顧REP和CCP原則,那么就可能由于連鎖發(fā)布反應,出現(xiàn)很多不必要的發(fā)布;如果只兼顧REP和CRP原則,那么就可能因為實現(xiàn)一個功能需要橫跨多個組件修改,造成過多的組件變更;如果只兼顧CCP和CRP,那我們可能就忘記了復用這檔子事兒,這在先前我們批判鮑勃大叔的時候已經(jīng)體現(xiàn)出來了。
小結(jié)
軟件系統(tǒng)的生命周期里處處充斥著約束條件,每多一個環(huán)節(jié)往往就會多一種矛盾,進而衍生出多個方向的約束。組件聚合張力圖反映的是發(fā)布和開發(fā)之間的矛盾,需要盡量遵循REP,CCP和CRP原則,滿足其約束,才能減少變更成本。
組件構(gòu)建過程中,除了聚合原則,還有耦合原則——描述的是組件的依賴關(guān)系。聚合原則告訴我們的是軟件系統(tǒng)中的最小元素,耦合原則說的是元素之間的關(guān)系,當這兩者和系統(tǒng)的功能結(jié)合到一起,就構(gòu)成一個運行著的系統(tǒng)[1]。系統(tǒng)是逐漸演化出來,即便我們熟知REP,CCP和CRP原則,也沒有辦法說,在系統(tǒng)構(gòu)建之初,遵循這些原則就能畫出完美的組件結(jié)構(gòu)圖。這便是“自頂而下”的設(shè)計不靠譜的基本解釋。
“自頂而下”的設(shè)計不靠譜還有更深層次的原因。本書的第14章“組件耦合”會有答案,且聽下回分解。
[1] 架構(gòu)整潔之道導讀(一)編程范式
[2] 架構(gòu)整潔之道導讀(三)組件耦合
于2018-10-28