原文:http://tutuge.me/2016/03/29/modular-and-component-summary/
前言
前段時間反復(fù)研讀了蘑菇街 App 的組件化之路、蘑菇街 App 的組件化之路·續(xù)和iOS應(yīng)用架構(gòu)談 組件化方案,然后又找到了其它一些研究組件化、模塊化方案的文章,但是總覺得差點什么,所以還是決定從頭開始思考。文章的標(biāo)題起的好寬泛,感覺給自己挖了個深坑-。-,其實只是自己對組件化、模塊化的一些看法、總結(jié)。
為什么
先總結(jié)下為什么要大動干戈的對代碼分模塊、拆組件。
代碼量膨脹,不利于維護,更不利于新功能的開發(fā)
現(xiàn)在隨便開發(fā)一個App的代碼行數(shù)都是數(shù)以萬計的,如果不對代碼做合理的拆分,那簡直就是災(zāi)難性的,估計只有最初的開發(fā)人員知道如何維護修改,如果換人開發(fā)的話,難以下手,更不用說開發(fā)新功能了。
不同業(yè)務(wù)代碼耦合嚴(yán)重,難以多人合作,職責(zé)不分明
多人一起開發(fā)時,如果代碼結(jié)構(gòu)、模塊化的不好,就很難對不同業(yè)務(wù)劃分出分界線,難以明確各自的職責(zé),牽一發(fā)動全身,出了問題更是容易相互扯皮(這個時候只能說一句“怪我咯o(╯□╰)o”),更不用提合并代碼時的沖突了。
所以,合理的組織代碼,劃分模塊、拆分組件是項目可以高效迭代的基礎(chǔ)。
疑問
那到底什么是模塊化、組件化?查資料的時候一會兒模塊,一會兒組件,有什么聯(lián)系,有什么區(qū)別?有人說這只是叫法習(xí)慣問題,知道大概意思就好,不用咬文嚼字,但是總覺得沒有個“定義”感覺不踏實,所以還是求助了萬能的維基百科=。=
模塊化
維基百科的Modular programming的開頭定義如下:
Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality.
接著,在Key aspects部分的開頭也說了:
With modular programming, concerns are separated such that modules perform logically discrete functions, interacting through well-defined interfaces.
可以總結(jié)為:模塊化的目的在于將一個程序按照其功能做拆分,分成相互獨立的模塊,以便于每個模塊只包含與其功能相關(guān)的內(nèi)容,模塊之間通過接口調(diào)用。
當(dāng)然,模塊化編程的具體概念是包含了很多內(nèi)容的,讀者可以詳細閱讀下維基百科的定義。
組件化
關(guān)于組件化,能找到的比較接近的就是維基百科的Component-based software engineering,其開頭內(nèi)容如下:
Component-based software engineering (CBSE), also known as component-based development (CBD), is a branch of software engineering that emphasizes the separation of concerns in respect of the wide-ranging functionality available throughout a given software system. It is a reuse-based approach to defining, implementing and composing loosely coupled independent components into systems.
乍一看,這不是跟模塊化Modular programming的定義很相似嘛=。=
的確,文中也提到組件化跟模塊化是很類似的,都是主要為了對一個系統(tǒng)做拆分,比如文中提到:
All system processes are placed into separate components so that all of the data and functions inside each component are semantically related (just as with the contents of classes). Because of this principle, it is often said that components are modular and cohesive.
同時,組件還具有其他屬性,如可替代性(substitutable),通過接口(interface)訪問,可重用性(Reusability)等,讀者可自行閱讀。
對比
難道模塊化跟組件化真的是完全一樣的?的確,很多時候兩者的概念完全可以相互替換,在實踐中更是經(jīng)?;煊谩?/p>
在求助谷歌,甚至閱讀了大量的前端技術(shù)等其它技術(shù)領(lǐng)域的組件化、模塊化的文章后,我覺得如果真要將它們兩者做個對比,大概總結(jié)如下:
- 模塊化強調(diào)的是拆分,無論是從業(yè)務(wù)角度還是從架構(gòu)、技術(shù)角度,模塊化首先意味著將代碼、數(shù)據(jù)等內(nèi)容按照其職責(zé)不同分離,使其變得更加容易維護、迭代,使開發(fā)人員可以分而治之。
- 組件化則著重于可重用性,不管是界面上反復(fù)使用的用戶頭像按鈕,還是處理數(shù)據(jù)的流程中的某個部件,只要可以被反復(fù)使用,并且進行了高度封裝,只能通過接口訪問,就可以稱其為“組件”。
當(dāng)然,并不是說模塊就不能被復(fù)用,還是要根據(jù)實際情況來看,使系統(tǒng)更加容易維護,開發(fā)更加方便,才是最終目的。
如何拆分
無論是模塊化還是組件化,首先肯定是做拆分,但是如何拆分?怎么下手?依照什么標(biāo)準(zhǔn)?
下面簡單總結(jié)一些方法。
橫向拆分業(yè)務(wù)、功能模塊
很多時候,一個完整的軟件程序是同時為多種業(yè)務(wù)服務(wù)的,所有可以優(yōu)先按照業(yè)務(wù)的不同,將整個系統(tǒng)進行拆分。
如一個電商類型的App,就可以分出商品瀏覽模塊、訂單模塊、購物車模塊、消息模塊、支付模塊等。又如微信這種社交型應(yīng)用,可以拆分出聯(lián)系人模塊、朋友圈模塊、聊天模塊、消息模塊等。
其實就是從用戶使用的角度,按照功能的不同劃分模塊,當(dāng)然,這種業(yè)務(wù)模塊是要由各種技術(shù)模塊作支撐的。

縱向拆分技術(shù)、架構(gòu)模塊
如果脫離業(yè)務(wù),只從技術(shù)角度來看,則可以嘗試縱向?qū)ο到y(tǒng)拆分模塊。
其實這里的縱向拆分跟對系統(tǒng)的架構(gòu)做分層有點像=。=,現(xiàn)如今只要需要聯(lián)網(wǎng)請求API的App都免不了有網(wǎng)絡(luò)請求、數(shù)據(jù)緩存、數(shù)據(jù)加工處理、數(shù)據(jù)展示、反饋用戶操作等行為,所有這些環(huán)節(jié)層層遞進才能完成一個功能。
當(dāng)開始著手規(guī)劃一個完整軟件系統(tǒng),或者說App時,就可以按照這些環(huán)節(jié)劃分模塊,縱向分層次的組合,搭建出一個以技術(shù)模塊組成的簡易系統(tǒng)架構(gòu)圖,方便后續(xù)的開發(fā),如下圖。

大體上的技術(shù)模塊劃分好以后,就可以按照具體的需求,實現(xiàn)每個技術(shù)模塊,乃至細分出更多的子模塊,如緩存模塊可能由鍵值對緩存(NSUserDefaults)、數(shù)據(jù)庫緩存(SQLite、Realm)、圖片緩存等子模塊組成,根據(jù)具體情況而定。
從界面入手,拆分可視化組件
現(xiàn)在再來看看如何從界面入手拆分可復(fù)用的組件。假如有如下布局的界面:

很多時候,像界面里面的“搜索框”、“頭像按鈕”、“內(nèi)容框”和顯示提示用的“加載中”HUD,甚至整個內(nèi)容的Cell,都是可能在很多地方出現(xiàn)的,而且本身的樣式、功能比較集中。
如頭像可能要支持點擊跳轉(zhuǎn),頭像圖片圓角,內(nèi)容框有特定的Padding和字體大小等,所以可以將這些界面上的元素“提”出來,單獨封裝成一個組件,供整個App復(fù)用?;蛘咧苯佑玫谌降慕M件,如圖中的“加載中”HUD,就可以用SVProgressHUD、MBProgressHUD等開源庫。
其實這里的組件有種sunnyxx大大提到過的“Self-Manager”的味道=。=,組件本身負責(zé)自己的所有功能、樣式,參考:iOS 開發(fā)中的 Self-Manager 模式。當(dāng)然跟前端的組件化也挺像的,如React里面的component,樣式、功能都封裝到component里面,以便更好地解耦復(fù)用。
從數(shù)據(jù)入手,拆分?jǐn)?shù)據(jù)加工組件
再來看看從數(shù)據(jù)入手,拆分可復(fù)用的組件。假如有如下數(shù)據(jù)處理流程:

其實大部分時候,拆分模塊、組件都是以清晰的流程、邏輯為基礎(chǔ)的,就如上圖的過程,當(dāng)流程清晰后,可以拆分復(fù)用的組件也就“出來了”。
如從JSON數(shù)據(jù)實例化出對應(yīng)的Entity對象,這個功能就是一個完整獨立的組件,當(dāng)然實際開發(fā)中會用Mantle、JSONModel等庫實現(xiàn)。
以此類推,校驗、格式化日期(如“幾秒鐘前、幾天前”)、多語言等環(huán)節(jié),都可以獨立成一個個的組件。
當(dāng)然,這里的組件一般是指能在多個模塊使用的功能組件,如果只是在某個界面上才用的,倒不如放到ViewModel、Presenter等這些直接跟界面有關(guān)的類里面。
小節(jié)
上面的幾種方法比較適合不知道如何下手時使用=。=,真正的開發(fā)中,還是要根據(jù)實際情況考慮,情況也會復(fù)雜些。不過倒是可以總結(jié)幾點原則:
- 單一職責(zé),意味著一個模塊、一個組件只做一件事,絕不多做。
- 正交性,意思是不重復(fù),一個模塊跟另一個模塊的職責(zé)是正交的,沒有重疊,組件也是一樣。
- 單向依賴,模塊之間最多是單向的依賴,如果出現(xiàn)A依賴B,B也依賴A,那么要么是A、B應(yīng)該屬于一個模塊,要么就是整體的拆分有問題。一個完整的軟件系統(tǒng)的模塊依賴應(yīng)該是一張有向無環(huán)圖。(當(dāng)然這是最終理想=。=)
- 緊湊性,模塊、組件對外暴露的接口、屬性應(yīng)該盡可能的少,接口的參數(shù)個數(shù)也要少。
- 面向接口,模塊、組件對外提供服務(wù)時最好是面向接口的,以便后期可以靈活的變更實現(xiàn)。