Android開發(fā)過程中,我們或許見過這樣的現(xiàn)象:
1. 上帝類頻頻出現(xiàn),有些類可能包含多個功能模塊的代碼
2. 臃腫類數(shù)不勝數(shù), 很多類里面可能少則1000行,多則幾千行的代碼
3. 不同功能混雜,類中包含不同層次的功能,難以查詢邏輯主線
4. 無節(jié)操的代碼重疊
以上現(xiàn)象,在我們開發(fā)中,尤其是接手別人代碼時,會顯示痛不欲生;那么我們來看一下上述現(xiàn)象的弊端有哪些 ?
1. 代碼查找 想一下找出需要的代碼,難!哪怕ctrl + F查找,都要分析好久!!!
2. 邏輯交叉混亂 想改什么東西,實在鼓不起勇氣!
3. 不同代碼齊聚一堂 鬼才知道添加或修改一個功能都要改多少地方,何況在不了解需? ? 求的前提下
如果頻頻遇到以上現(xiàn)象,或許我們就應(yīng)該想了,到底什么樣的代碼,才是所謂的好代碼呢? 有沒有什么量化的標(biāo)準(zhǔn) ? 又如何來很好的避免上述的問題 ?
答案是**規(guī)范**與**標(biāo)準(zhǔn)設(shè)計**
首先談一下規(guī)范,不詳談,不矯情,建議參考Alibaba推出的Java開發(fā)規(guī)范,請走這里:
? 這里主要詳談的是設(shè)計,首先來談一下開發(fā)出高內(nèi)聚,低耦合,層次分明,可讀性高的代碼,需要怎么做 ?
? **1. 分清代碼職能**
? 想要添加一段代碼,首先必須明確這段代碼是用來做什么, 這段代碼的功能是什么類型的功能 ? 這是一段高度反映業(yè)務(wù)的代碼?或者是可復(fù)用型的獨立業(yè)務(wù)功能代碼?又或是與業(yè)務(wù)完全獨立的常規(guī)輔助類型的代碼? 只有明晰了這段代碼的功能與類型,才能映射出更好的設(shè)計。
? **2. 初步劃分模塊**
? 模塊劃分,是系統(tǒng)源碼合理劃分的最有效的途徑之一。但模塊的劃分不是必須的,項目越大,模塊劃分帶來的益處越大;同時關(guān)注點的劃分也越難。小規(guī)模的項目,完全可以越過這一步。
? **3. 初步層次劃分**
? 如果說,模塊的劃分可以把一個大的系統(tǒng)有效地劃分為幾個子系統(tǒng);那么針對不同的子系統(tǒng),同樣包含有大量的代碼;因此層次劃分的重要性就不言而喻了;至于這個的使用,根據(jù)業(yè)務(wù)進行自我分析判斷;一般而言,層次劃分是必要的,如當(dāng)前流行的MVC, MVP, MVVM等框架
? **4. 功能屬性劃分**
? 和1類似,我們需要明白一點,我們的代碼有幾種類型? 總體來區(qū)分,分為Common與Business;我感覺Business下還可以進行劃分,有些Business是需要復(fù)用,不同模塊都需要的, 我稱之為**弱業(yè)務(wù)型**;有些Business代碼沒法重用,我稱之為**強業(yè)務(wù)型**。因此,我的功能劃分包括: **通用型**, **弱業(yè)務(wù)型**以及**強業(yè)務(wù)型**。
? **5. 單一職責(zé)原則**
? 單一職責(zé)原則,作為面向?qū)ο蟮?大設(shè)計原則之一,極大的保證了不會產(chǎn)生邏輯交叉混亂與上帝類。也是日常開發(fā)中必須遵守的原則。在阿里巴巴的**《Java開發(fā)手冊》**中,也提到了這點,概括來說,就是保證大到每一個子系統(tǒng),每一個模塊,每一個分包,小到每一個類,每一個方法都僅執(zhí)行單一的功能;這個功能也就是關(guān)注點的劃分,需要根據(jù)開發(fā)經(jīng)驗來區(qū)分,模塊的劃分與方法的劃分必然是不同的,而且相去甚遠(yuǎn)。
? **6.? 復(fù)用性與擴展性**
? 代碼復(fù)用性, 相信每一個人都對這個原則有深刻的理解;復(fù)用性可以極大的提高開發(fā)效率, 當(dāng)然必須要配合擴展性,才能更好的達到目的。擴展性越強,復(fù)用范圍就越廣,該模塊或方法就越具有原子性。
? **7. 封裝性**
? 在一個頁面中,我們可能需要實現(xiàn)形形色色的需求;如果把這些需求都放在Activity中,那么可能最后Activity的規(guī)模就可想而知了;除了通過分層把不屬于Activity的邏輯劃分出去(如MVP中將邏輯放到了P),余下一些的View實現(xiàn),也需要大量的代碼來構(gòu)建;這時候我們可以再次劃分不同的模塊,如TitleBar我們可以根據(jù)業(yè)務(wù)定義自己的TitleBar, 只暴露一些基本的配置,如setTitle()等;然而有時TitleBar中也會包含大量的業(yè)務(wù)邏輯,這時我們可以在TitleBar UI層封裝的基礎(chǔ)上,加入一個TitleBarDao來處理TitleBar相關(guān)的實現(xiàn),將這塊邏輯繼續(xù)分發(fā)到TitleBarDao中。
? ? 以上回答了如何更好的設(shè)計一段代碼,才能更好的保證其邏輯層次的清晰, 解耦性以及可讀性,都是個人愚見;代碼設(shè)計是根據(jù)不同業(yè)務(wù),不同規(guī)模進行合時宜的設(shè)計,萬不可為了設(shè)計而造成過度設(shè)計。設(shè)計的目的是提高開發(fā)效率,提高維護與測試效率。說了這么多,如何來實現(xiàn)呢 ? 以下是一些簡單的實例。
? 1) 添加一個View,View顯示的寬度是屏幕的3/4
? ? View targetView;
? ? LayoutParams lp = targetView.getLayoutParams();
? ? lp = 3 * ...獲取屏幕寬度 / 4;
? ? targetView.setLayoutParams(lp);
? 以上這段代碼,很簡單,相信很多人也寫過;這段代碼首先我們分析功能實現(xiàn): View寬度的設(shè)計 和屏幕寬度獲取; 這是一段通用型的代碼,因此我們可以放在通用層中,方法內(nèi)部包含屏幕寬度獲取的實現(xiàn),因此根據(jù)單一職責(zé)原則,我們可以將之抽取出來。
? 2) string, dimen, style文件
? 這些文件的使用,就是為了將文本, 尺寸, 樣式等抽取出來,實現(xiàn)單一的目的。
? 3) 組件化,插件化
? 這兩種架構(gòu)方式是大系統(tǒng)規(guī)模的代碼廣義層面的架構(gòu)方式,將一個大系統(tǒng)分為若干子系統(tǒng),子模塊來管理;
? 4) MVC, MVP, MVVM
? 這些架構(gòu)方式,則屬于從小的層級進行代碼分層劃分,劃分為不同的層次,每個層次實現(xiàn)單一職責(zé)。
說了這么多,代碼架構(gòu)的目的是什么?
1. 解耦
2. 復(fù)用
3. 可讀性
4. 健壯性
5. 提高并行開發(fā)效率
最近出了很多架構(gòu)思想,無論是最初的MVC, 還是近幾年風(fēng)頭極盛的MVP, Google推出的MVVM,還是系統(tǒng)層面的組件化,模塊化,插件化;最終遵循的架構(gòu)原則無非就是三點:
1. 橫向劃分模塊
2. 縱向劃分層次
3. 解耦通信
1. 橫向分塊
一個大的項目,代碼量是極大的,甚至達到1G以上;這時必須先以大的層級進行劃分,才能有效達到分離的目的。現(xiàn)在流行的模塊化思想便有了用武之地;模塊化思想主張將一個系統(tǒng)橫向切分為不同的子系統(tǒng)或者可以稱之為模塊,根據(jù)業(yè)務(wù)與開發(fā)需要將工程劃分為Common模塊, Business-01模塊, Business-02模塊... 等,同時不同的模塊之間從大的層級實現(xiàn)職責(zé)單一原則。
2. 模塊之間解耦
?一個系統(tǒng)劃分為若干子系統(tǒng),模塊后,不同的模塊之間存在交叉通信是必然的;那么如何實現(xiàn)模塊之間解耦呢?
?組件化的解耦**思路是實現(xiàn)一套注冊路由機制,根據(jù)一套統(tǒng)一的注冊路由系統(tǒng)來統(tǒng)一實現(xiàn)跨組件通信,當(dāng)前比較流程的框架有ActivityRouter,? ?ARouter等
插件化的解耦**則直接通過Android系統(tǒng)提供的Binder機制來進行
3. 縱向分層
從系統(tǒng)層級做了模塊劃分后,每個模塊必然也需要自己的架構(gòu),這時候就需要使用分層的思想了,也就是要說的縱向分層策略。MVC, MVP, MVVM,F(xiàn)lutter這些設(shè)計則使用了縱向分層的思想;它們分別將一段代碼或一個頁面劃分為3個層級:
Model層:? ? ? 處理數(shù)據(jù)
View層:? ? ? 處理UI顯示
MVC的Controller, MVP的Presenter, Flutter的Store? ? ?則處理主要邏輯
經(jīng)以上劃分后,數(shù)據(jù)處理, Ui顯示, 邏輯處理都放到了各自的層進行處理,每個層僅執(zhí)行本層應(yīng)該做的操作。分層后,代碼已經(jīng)做到了結(jié)構(gòu)清晰,盡可能的解耦, 易于理解維護。不過,在開發(fā)中,Model返回的數(shù)據(jù),不一定是我們想要的格式; 而且將異步放到Model層,則Model除了處理數(shù)據(jù)以外,還要進行不同的Async處理。在以上想法下,我們可以繼續(xù)劃分:
Model層:? ? 處理數(shù)據(jù)(請求, 數(shù)據(jù)庫, 緩存等)
Work層:? ? ? 處理異步相關(guān)
Presenter層:? 處理核心控制邏輯
Converter層:? 數(shù)據(jù)轉(zhuǎn)化層 將Model數(shù)據(jù)轉(zhuǎn)化為UI需要的格式
4. 層次間解耦
根據(jù)CleanArchitecture原則,采用圓蔥形式的分層與解耦策略,因此在設(shè)計時注意兩點:
1) 明確的層次限定
2) 禁止跨層次調(diào)用
3) 從內(nèi)到外單向調(diào)用
根據(jù)洋蔥結(jié)構(gòu),由內(nèi)到外我們可以得到Model -> Work -> Converter -> Presenter -> UI;在調(diào)用時,某層只能調(diào)用向內(nèi)的鄰接層,如Presenter只能調(diào)用Work層(存在異步操作時)或Model層(無異步操作,甚至無異步操作也可以在Work中封裝一層),而不能出現(xiàn)Model層調(diào)用Presenter層的現(xiàn)象;相同的,UI層只能調(diào)用Presenter層,而不能跨層調(diào)用Model層;Converter作為外圍輔助層,不直接參與洋蔥結(jié)構(gòu)
層通信方式
層與層之間的通信,必然不能通過直接調(diào)用的方式(特別是View層與Presenter層),我們可以參考MVP模式中的**接口通信方式**或者Flutter模式中的**Event路由模式**來實現(xiàn)。
以登錄功能為例,我們需要以下功能
參數(shù)校驗
加載
登錄請求
返回處理
取消加載
顯示結(jié)果
參數(shù)校驗, 參數(shù)的具體信息來自View層,但是其本身功能為邏輯處理,因此適合在Presenter層
加載, 取消加載 涉及到Activity Context來加載Dialog,因此適合在View層
登錄請求, 毫無爭議放在Model與Work層,如有參數(shù)轉(zhuǎn)化,需要加入Converter層
返回處理, 毫無爭議放在Presenter層
顯示結(jié)果, 涉及到Toast顯示以及提示,必然在View層
有些實現(xiàn)可以涉及Context且本身屬于Presenter處理,不好區(qū)分;因此建議通過Context類型區(qū)分, 涉及必須使用Activity Context如Toast, Dialog, PopupWindow則在View層, AplicationContext亦可的則可以放在Presenter層。
本篇文章從理論方面講解了Android架構(gòu)一些常用知識與理念,且純屬個人愚見,有爭議者可提出一并討論;目的就是找到一個可以合理解決大部分問題且適合移動端的開發(fā)架構(gòu)。