隨筆:組件化的一些思考

筆者所在部門都一個(gè)很大的SDK,被公司眾多部門所使用勋乾,筆者到來之前宋下,這個(gè)SDK采取的是原始的開發(fā)方式,隨著時(shí)間的遷移辑莫,已經(jīng)變得非常大学歧,大概一千多個(gè)OC文件,C++的沒數(shù)過各吨,功能也很豐富枝笨,但是對于某些部門,對包大小有非常嚴(yán)格的限制揭蜒,某些不需要的功能需要減包横浑,對于一個(gè)高度耦合在一起的SDK來說,幾乎只能通過預(yù)編譯宏來減少文件的引用屉更,這樣做造成一個(gè)git倉庫上維護(hù)著多個(gè)分支徙融,每次合并都是噩夢般地存在,而且由于功能模塊很多瑰谜,對于每一個(gè)功能模塊都需要有一個(gè)預(yù)編譯宏欺冀,這樣帶來一個(gè)問題,如果宏的組合發(fā)生錯(cuò)誤萨脑,那么這個(gè)極有可能帶來嚴(yán)重BUG隐轩。因?yàn)楣P者面試的時(shí)候,和面試官除了常規(guī)技術(shù)問題渤早,談得最多的是架構(gòu)設(shè)計(jì)职车,所以當(dāng)筆者進(jìn)入公司之后,總監(jiān)以及組長都希望我能夠解決掉現(xiàn)在的問題鹊杖,至少說能夠部分解決提鸟,特別是大ZT計(jì)劃發(fā)布之后,這件事被真正意義上的開始實(shí)施仅淑。筆者這篇文章是一篇隨筆称勋,是希望為遇到同樣的事情的開發(fā)者提供一些思路,由于保密的原因涯竟,有很多東西沒有辦法說清楚赡鲜,忘見諒空厌,勿噴。

筆者的任務(wù)在初期是比較簡單和明確的银酬,要求對于某些模塊能夠可選集成嘲更,使用cocoaPod的時(shí)候,是否集成某個(gè)模塊揩瞪,不能改動一句代碼赋朦,有點(diǎn)像友盟的社會組件集成方式,但是比那個(gè)應(yīng)該要復(fù)雜一些李破,后面會細(xì)說原因宠哄。

看到這里,有經(jīng)驗(yàn)的朋友第一個(gè)想到的就是組件化嗤攻,組件化已經(jīng)不是什么稀罕事兒了毛嫉,業(yè)內(nèi)有非常成熟的URL注冊方案,Mediator方案以及Protocol注冊方案妇菱。但是這些成熟的方案其實(shí)都是針對App的承粤,App的組件化其實(shí)是比較簡單的,因?yàn)锳pp有可視化的邏輯幫助我們對整個(gè)App進(jìn)行縱向切割和橫向切割闯团,工具與業(yè)務(wù)之間的界限非常明確辛臊,業(yè)務(wù)與業(yè)務(wù)模塊之間的界限也非常明顯,特別是在復(fù)合設(shè)計(jì)模式的幫助下房交,代碼的可復(fù)用性變得很大彻舰。筆者認(rèn)為,橫向切割解決的是組件的依賴方向問題涌萤,縱向切割是解決組件間的黑盒問題淹遵,橫向切割完成之后口猜,位于上層的組件可以依賴下層负溪,而下層不能反向依賴,縱向切割完成之后位于同一橫向的組件不能夠直接通訊济炎,這是差不多有經(jīng)驗(yàn)的開發(fā)者都能夠想明白的事情川抡。但是筆者所遇到的問題是

第一:一個(gè)巨型SDK,在某種意義上本身就屬于工具须尚,其界線劃分變得模糊崖堤,筆者在以前的公司集成過融云的SDK,融云的SDK分為兩部分耐床,UI和通訊功能部分密幔,界線非常明顯。但是筆者這邊麻煩的是撩轰,沒有UI胯甩,而且筆者部門的SDK涉及的是圖像處理領(lǐng)域以及人工智能領(lǐng)域昧廷,連像App那樣抽出基礎(chǔ)工具類都變得異常麻煩,這個(gè)麻煩點(diǎn)來自于偎箫,這個(gè)基礎(chǔ)工具類可能從邏輯角度都說不通它算是基礎(chǔ)工具木柬,筆者也是為了避免一個(gè)問題,基礎(chǔ)工具類很有可能會變成垃圾堆淹办。

第二:它沒有眉枕,并且它沒有辦法使用現(xiàn)在成熟的復(fù)合設(shè)計(jì)模式,例如MVC怜森,MVVM等速挑,復(fù)合設(shè)計(jì)模式帶來的最大的好處是我們能夠在某種規(guī)范下劃分代碼。如第一個(gè)條件所說塔插,我們的SDK沒有UI梗摇,M肯定是有的,但是C也變得很模糊想许,例如筆者舉個(gè)例子伶授,渲染鏈讀者們認(rèn)為應(yīng)該屬于什么?人工智能那堆C++代碼流纹,橋接代碼就更不用說了糜烹。

第三:渲染鏈中一幀數(shù)據(jù)在A模塊的輸出會影響的B模塊該以何種方式處理這一幀的數(shù)據(jù),而B模塊對本幀數(shù)據(jù)不同處理方式又需要告知A模塊下一幀數(shù)據(jù)該如何處理漱凝,這就讓整個(gè)渲染鏈的通訊方向變成了雙向的疮蹦。在傳統(tǒng)開發(fā)模式下,正向通訊采用屬性傳值或者調(diào)用方法的方式通訊茸炒,逆向通訊采用通知愕乎,代理,Block壁公,因?yàn)楣P者需要實(shí)現(xiàn)可選集成感论,所以代理和Block不能使用,通知是不可能用的紊册,否則這么多模塊的通訊比肄,會讓通知根本沒法維護(hù)。

第四:再說那三種業(yè)內(nèi)比較成熟的組件化方案囊陡,URL注冊方案芳绩,我見過某大型App用過,這東西很簡單撞反,通過參數(shù)和需要參數(shù)的對象(大多數(shù)情況下是ViewController)組建一個(gè)URL妥色,然后傳給Router去解析URL并完成組件的創(chuàng)建和傳值。這方案非常適合組件非常獨(dú)立的情況遏片,但是對于復(fù)雜情況來說嘹害,應(yīng)該有同學(xué)也會贊同鳍侣,復(fù)雜通訊情況下,這種方案會變得很無力吼拥,而且創(chuàng)建URL方式進(jìn)行通訊倚聚,特別是逆向通訊,非常無力凿可。Mediator方案惑折,我沒用過,但是我看過網(wǎng)上很多關(guān)于這套方案的一些框架封裝或者Demo枯跑,這種方案核心就是Mediator的分類惨驶,由分類調(diào)起服務(wù),但是這樣的話敛助,我們來思考一個(gè)問題粗卜,如果一個(gè)Mediator的分類對一個(gè)組件負(fù)責(zé),而這個(gè)組件本身功能比較獨(dú)特纳击,那么這個(gè)分類寫出來對外暴露的接口也會有一定的獨(dú)特性续扔,而該Mediator的分類對模塊的通訊也會變得具有獨(dú)特性,會造成Mediator的分類對模塊的強(qiáng)依賴關(guān)系焕数,這不像在App組件化中纱昧,通過類名創(chuàng)建類那么簡單。就算解決了這個(gè)問題堡赔,但是如果不集成某個(gè)功能模塊识脆,這個(gè)Mediator的分類是否應(yīng)該被移除?如果移除善已,會造成方法找不到灼捂,編譯不過,如果不移除换团,那么就會存在一大堆Mediator悉稠,對于連幾K包增量都要計(jì)較的團(tuán)隊(duì),顯然是他們不能接受的啥寇,這就不符合我們在計(jì)劃之出偎球,對于功能模塊可選集成的要求洒扎。Protocol注冊方案是筆者認(rèn)為最接近能夠?qū)崿F(xiàn)筆者要求的方案辑甜,但是問題來了,誰來遵循這個(gè)Protocol(后面還會細(xì)說)袍冷?但是無論哪種組件化方案,似乎我們都需要一個(gè)中介者,無論是Router還是Mediator卷胯。

但是箭在弦上,不得不發(fā)淌友,筆者具體做法如下。

第一步:清理整個(gè)SDK骇陈,這一點(diǎn)是常規(guī)操作震庭,依據(jù)經(jīng)驗(yàn),一個(gè)經(jīng)歷了幾年迭代的任何代碼工程你雌,肯定有廢棄代碼器联,廢棄文件以及無效依賴,由于我們的SDK影響非常大婿崭,所以筆者不得不采取人肉方式進(jìn)行清理拨拓,這樣清理后也放心,先找廢棄文件氓栈,廢棄文件會有一個(gè)特征渣磷,它的依賴鏈會斷,也就是你順著依賴往上找授瘦,會發(fā)現(xiàn)在某個(gè)時(shí)候斷掉了或者直接根本就沒人依賴它醋界。無效依賴是指某個(gè)文件可能import了另外一個(gè)文件,但是卻沒有使用到這個(gè)文件的任何東西提完。清理會分幾波物独,因?yàn)殡S著清理的進(jìn)行,越來越多廢棄的東西會暴露出來氯葬,這樣是為以后做準(zhǔn)備挡篓。申明一點(diǎn),這和App不同的是帚称,App由于是可視化的官研,哪個(gè)界面沒用了,一目了然闯睹,隨之的ViewController戏羽,View,Model都會被廢棄到楼吃。

第二步:橫向切割始花,先確定引用方向,我根據(jù)我們的數(shù)據(jù)的流向從下至上(注:{}表示是同一橫向?qū)樱┓譃閧AI的C++庫孩锡,Base庫(這一層做架構(gòu)把控的一定要嚴(yán)格控制好里面的文件酷宵,否則一定會變成垃圾堆,我們的base層只存放基類和線程模型)} -> {中介者庫,AI封裝庫,渲染擴(kuò)展庫(主要是openGLES和metal之間的通訊)} -> {算法庫(老大經(jīng)常說躬窜,我們的核心科技敖娇选!H侔ぁD腥汀)朴摊,能力庫(也可以理解為業(yè)務(wù)層類似的東西)} -> {能力組裝庫},注意此虑,這里的數(shù)據(jù)的流向成為了我橫向切割的重要依據(jù)甚纲,而不再使用App根據(jù)業(yè)務(wù),UI朦前,基礎(chǔ)功能贩疙,基礎(chǔ)工具這樣的類似劃分。

第三步:縱向切割况既,這個(gè)沒有什么太多說的这溅,根據(jù)業(yè)務(wù)或者功能劃分邏輯來,剛才分好的橫向?qū)与m然在縱向有了粗略的切割棒仍,但是不夠悲靴,例如人工智能AI模塊,我們有五六種人工智能能力莫其,而C++庫加上AI模型癞尚,是很大的,一個(gè)人工智能能力加起來可能就是幾百兆了乱陡,所以還要分浇揩,但是這里早就有大神提到過,縱向粒度劃分一定要合適憨颠,劃分得太粗胳徽,相當(dāng)于沒怎么劃分,劃分得太細(xì)爽彤,給自己找麻煩养盗,同時(shí)要結(jié)合收益是否和代價(jià)相匹配,除非非常明顯的劃分界限适篙,一般不要分開往核,舉個(gè)簡單例子,可能不太準(zhǔn)確嚷节,圖形識別和文字識別界線非常明確聂儒,應(yīng)該劃分掉,前景貼紙貼花和前景貼紙貼草硫痰,這個(gè)界線就不那么明確衩婚,所以就不要劃分。

第四步:做到這里的時(shí)候碍论,筆者其實(shí)還是沒有一個(gè)非常完美的中介者方案谅猾,但是有一個(gè)保底方案柄慰,回到我剛才面臨的問題中的第四點(diǎn)鳍悠,“Protocol注冊方案是筆者認(rèn)為最接近能夠?qū)崿F(xiàn)筆者要求的方案税娜,但是問題來了,誰來遵循這個(gè)Protocol(后面還會細(xì)說)”藏研,這里有一個(gè)比較好的解決方案是對于一個(gè)組件來說敬矩,每個(gè)組件要有一個(gè)其統(tǒng)一封裝類,這個(gè)統(tǒng)一封裝類對整個(gè)模塊負(fù)責(zé)蠢挡,通過這個(gè)統(tǒng)一封裝能掉到這個(gè)模塊的所有功能(當(dāng)然有些功能不是直接調(diào)用弧岳,而是通過其他類轉(zhuǎn)調(diào)一層),有這個(gè)統(tǒng)一封裝類來遵循這個(gè)模塊的特有協(xié)議业踏。筆者在這個(gè)時(shí)候已經(jīng)有了一個(gè)保底方案禽炬,就是把這個(gè)統(tǒng)一封裝對外所有暴露的方法和屬性寫進(jìn)這個(gè)Protocol中,然后把這個(gè)Protocol放入到Base層勤家,模塊間通訊通過類似于[id<protocol>manager XXXXX:XXX];這樣的方式調(diào)用腹尖,這樣就把強(qiáng)依賴變成了弱依賴,是可以實(shí)現(xiàn)可選集成的伐脖,但是隨之而來的要求讓這個(gè)方案徹底被否決热幔。

這里是一個(gè)插曲,幾個(gè)大部門老大聯(lián)合發(fā)話(主要是針對我們部門讼庇,我們部門的SDK最大绎巨,影響最深),不僅要實(shí)現(xiàn)功能可選集成蠕啄,還要實(shí)現(xiàn)可擴(kuò)展场勤,例如AI能力,我們現(xiàn)在有五六種歼跟,以后出現(xiàn)新的能力却嗡,能夠通過某種規(guī)則非常簡單增加一種AI能力,不能對現(xiàn)有代碼進(jìn)行改動嘹承。剛才哪種方案很明顯不行了窗价,因?yàn)槲覀冃枰谏蠈又匦聦懻{(diào)用Protocol的代碼。
第五步:不得不承認(rèn)叹卷,我這里受到了阿里的BeeHive團(tuán)隊(duì)的啟發(fā)并進(jìn)行了深度改造撼港,由于有新增能力不得對現(xiàn)有代碼進(jìn)行改動的要求,這就決定了骤竹,我的中介者不止有一個(gè)帝牡,是多個(gè)中介者,例如對AI能力有一個(gè)中介者蒙揣,對所有AI能力負(fù)責(zé)靶溜,能力庫有一個(gè)中介者,能力組裝庫也有一個(gè)中介者。這個(gè)中介者的有一個(gè)作用罩息,上層對下層調(diào)用的統(tǒng)一入口嗤详,數(shù)據(jù)傳輸給中介者,由中介者對該層的所有組件進(jìn)行數(shù)據(jù)分發(fā)瓷炮,組件數(shù)據(jù)處理結(jié)束之后葱色,再由中介者統(tǒng)一收集,由上層自行拿去娘香,第二個(gè)不同的地方是苍狰,BeeHive方法的核心是每個(gè)組件有一個(gè)Protocol,作為標(biāo)識符性質(zhì)的協(xié)議烘绽,我這里有了一個(gè)BaseProtocol規(guī)范該層所有組件淋昭,SpecialProtocol用以作為標(biāo)識符性質(zhì)的協(xié)議,為什么用協(xié)議安接,不用字符串响牛,是為了擴(kuò)展。在SDK啟動之初赫段,所有的組件向該層中介者進(jìn)行注冊呀打,告知中介者,我們有這個(gè)能力(這里我們經(jīng)過線程和算法的優(yōu)化糯笙,不必?fù)?dān)心性能問題)贬丛。于是調(diào)用邏輯變成了這樣,當(dāng)數(shù)據(jù)來臨的時(shí)候给涕,最上層的中介者拿到數(shù)據(jù)分發(fā)給所有組件(如果沒有該組件豺憔,對nil對象發(fā)送消息,不會崩潰够庙,這個(gè)大家都知道)恭应,所有組件根據(jù)自己需要的東西向下一層的中介者發(fā)送數(shù)據(jù)并等待結(jié)果,下一層的中介者再分發(fā)給本層的所有組件耘眨,到最底層處理結(jié)束后又由反方向返回?cái)?shù)據(jù)昼榛,最終將在最上層中介者告訴App,由于有BaseProtocol的存在剔难,所以每一層的中介者對于本層的所有組件都是弱依賴胆屿,又由于SpecialProtocol的存在,能夠讓中介者準(zhǔn)確地分發(fā)給對應(yīng)組件偶宫,這個(gè)BaseProtocol能夠存在得益于我第二非迹,三步橫縱向切割的正確性,如果切割不對纯趋,這個(gè)BaseProtocol將無法存在憎兽。

補(bǔ)充說明:
1:關(guān)于數(shù)據(jù)傳遞這一塊冷离,我非常贊同Mediator作者關(guān)于數(shù)據(jù)傳遞需要去模型化傳遞的思想,如果一個(gè)自定義模型需要被傳遞(首先這里已經(jīng)不存在URL關(guān)于非常規(guī)參數(shù)無法傳遞的問題)纯命,需要去模型化西剥,還有其他幾種情況,如果在開發(fā)過程中扎附,如果一個(gè)數(shù)據(jù)被大量使用蔫耽,是可以下沉到基礎(chǔ)層之類的地方去结耀,如果僅有少部分組件實(shí)用留夜,我目前的想法是我寧可復(fù)制一個(gè)類出來,如果讀者有更好的方案图甜,歡迎討論碍粥。同時(shí)需要注意一點(diǎn)的是,如果某種特殊情況需要實(shí)現(xiàn)例如A->B->C這樣的傳遞黑毅,其中A和C需要使用到該數(shù)據(jù)嚼摩,B只需要傳遞就行了的情況,那么只有A和C需要知道具體類型矿瘦,B是可以使用多態(tài)的枕面,這在某些情況下非常實(shí)用。

2:關(guān)于同一層級組件如果需要進(jìn)行通訊缚去,在我的案例里面潮秘,通過中介者,拿到另一個(gè)組件相關(guān)數(shù)據(jù)(去模型化的)

3:單一設(shè)計(jì)原理易结,不要出現(xiàn)類似于common這樣的東西枕荞,這會成為耦合的重災(zāi)區(qū)

4:淺繼承,繼承是面向?qū)ο笳Z言的優(yōu)勢搞动,但是也是劣勢躏精,會出現(xiàn)強(qiáng)耦合和超級類,cat大神在說Swift面向協(xié)議編程的時(shí)候說過這個(gè)問題

由于我們的SDK對公司內(nèi)都不開源鹦肿,所以我沒有辦法把事情講得很清楚矗烛,更不可能貼代碼,如果我們有開源的那一天箩溃,希望能和大家細(xì)細(xì)討論高诺,我最滿意的是能力組裝層的Center-Classify遞歸分發(fā)的設(shè)計(jì)。最后碾篡,沒有最好的設(shè)計(jì)虱而,只有最合適的設(shè)計(jì)。勿噴开泽,謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牡拇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惠呼,老刑警劉巖导俘,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剔蹋,居然都是意外死亡旅薄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門泣崩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來少梁,“玉大人,你說我怎么就攤上這事矫付】Γ” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵买优,是天一觀的道長妨马。 經(jīng)常有香客問我,道長杀赢,這世上最難降的妖魔是什么烘跺? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮脂崔,結(jié)果婚禮上滤淳,老公的妹妹穿的比我還像新娘。我一直安慰自己脱篙,他們只是感情好娇钱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绊困,像睡著了一般文搂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秤朗,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天煤蹭,我揣著相機(jī)與錄音,去河邊找鬼取视。 笑死硝皂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的作谭。 我是一名探鬼主播稽物,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼折欠!你這毒婦竟也來了贝或?” 一聲冷哼從身側(cè)響起吼过,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咪奖,沒想到半個(gè)月后盗忱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羊赵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年趟佃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昧捷。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闲昭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出料身,到底是詐尸還是另有隱情汤纸,我是刑警寧澤衩茸,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布芹血,位于F島的核電站,受9級特大地震影響楞慈,放射性物質(zhì)發(fā)生泄漏幔烛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一囊蓝、第九天 我趴在偏房一處隱蔽的房頂上張望饿悬。 院中可真熱鬧,春花似錦聚霜、人聲如沸狡恬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弟劲。三九已至,卻和暖如春姥芥,著一層夾襖步出監(jiān)牢的瞬間兔乞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工凉唐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庸追,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓台囱,卻偏偏與公主長得像淡溯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子簿训,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容