前言
關(guān)于Android架構(gòu)恰梢,可能在很多人心里一直都是虛無縹緲的存在佛南,似懂非懂、為了用而用嵌言、處處生搬硬套嗅回,這種情況使用的意義真的很有限。本人有多個(gè)項(xiàng)目重構(gòu)的經(jīng)驗(yàn)摧茴,恰好對(duì)設(shè)計(jì)領(lǐng)域較為感興趣绵载,今天我將毫無保留的將自己對(duì)架構(gòu)、設(shè)計(jì)的理解分享給大家苛白。
本文不會(huì)具體去講什么是MVC娃豹、MVP、MVVM
购裙,但我描述的點(diǎn)應(yīng)該都是這些模式的基石
懂版,從本質(zhì)上講明白為什么
這樣做,這樣做的好處
是什么躏率,有了這些底層思想
的支持再去看對(duì)應(yīng)的架構(gòu)模式
躯畴,相信會(huì)讓你有一種煥然一新的感覺民鼓。
知識(shí)儲(chǔ)備:需掌握Java面向?qū)ο蟆⒘笤O(shè)計(jì)原則
私股,如果不理解也無妨摹察,我盡量將用到的設(shè)計(jì)原則加以詳細(xì)描述
目錄
-
1. 模塊化的意義何在?
- 1.1 基本概念以及底層思想
- 1.2 我們要基于哪些特性去做模塊化劃分倡鲸?
- 1.3 Android如何做分層處理供嚎?
- 1.4 Data Mapper或許是解藥
- 1.5 無處安放的業(yè)務(wù)邏輯
-
2. 合理分層是給 數(shù)據(jù)驅(qū)動(dòng)UI 做鋪墊
- 2.1 什么是 控制反轉(zhuǎn)?
- 2.2 什么是數(shù)據(jù)驅(qū)動(dòng)UI峭状?
- 2.3 為什么說數(shù)據(jù)驅(qū)動(dòng)UI底層思想是控制反轉(zhuǎn)克滴?
- 2.4 為什么引入Diff?
-
3. 為什么我建議使用 函數(shù)式編程
- 3.1 什么是 函數(shù)式編程优床?
- 3.2 Android視圖開發(fā)可以借鑒函數(shù)式編程思想
1. 模塊化的意義何在劝赔?
1.1 基本概念以及底層思想
所有的模塊化都是為了滿足單一設(shè)計(jì)原則 (字面意思理解即可),一個(gè)函數(shù)或者一個(gè)類再或者一個(gè)模塊胆敞,職責(zé)越單一復(fù)用性就越強(qiáng)着帽,同時(shí)能夠間接降低耦合性
在軟件工程的背景下,改動(dòng)就會(huì)有出錯(cuò)的可能移层,不要說"我注意一點(diǎn)就不會(huì)出錯(cuò)"
這種話仍翰,因?yàn)槿瞬皇菣C(jī)器。我們能做的就是盡可能讓模塊更加單一观话,職責(zé)越單一影響到外層模塊的可能性就越小予借,這樣出錯(cuò)的概率也就越低。
所以模塊化核心思想即:單一設(shè)計(jì)原則
1.2 我們要基于哪些特性去做模塊化劃分频蛔?
做模塊化處理的時(shí)候盡量基于兩種特性進(jìn)行功能特性
灵迫、業(yè)務(wù)特性
功能特性
網(wǎng)絡(luò)、圖片加載等等都可稱之為功能特性晦溪。比如網(wǎng)絡(luò):我們可以將網(wǎng)絡(luò)框架的集成瀑粥、封裝等等寫到同一個(gè)
模塊(module、package等)
當(dāng)中三圆,這樣可以增強(qiáng)可讀性(同一目錄一目了然)
狞换、降低誤操作
概率,方便于維護(hù)也更加安全嫌术。同時(shí)也可將模塊托管至遠(yuǎn)程如maven庫哀澈,可供多個(gè)項(xiàng)目使用牌借,進(jìn)一步提升復(fù)用性
業(yè)務(wù)特性
業(yè)務(wù)特性字面意思理解即可度气,就是我們常常編寫的業(yè)務(wù),需要以業(yè)務(wù)的特性進(jìn)行模塊劃分
為什么說業(yè)務(wù)特性
優(yōu)先級(jí)要高于功能特性
膨报?
舉個(gè)例子如下圖:
相信很多人見過或者正在使用這種分包方式磷籍,在業(yè)務(wù)層把所有的Adapter
适荣、Presenter
、Activity
等等都放在對(duì)應(yīng)的包中院领,這種方式合理嗎弛矛?先說答案不合理
,首先這已經(jīng)是在業(yè)務(wù)層比然,我們做的所有事情其實(shí)都在為業(yè)務(wù)層服務(wù)丈氓,所以業(yè)務(wù)的優(yōu)先級(jí)應(yīng)該是最高的,我們應(yīng)當(dāng)優(yōu)先根據(jù)業(yè)務(wù)特性將對(duì)應(yīng)的類放入到同一個(gè)包中强法。
功能模塊
核心是功能万俗,應(yīng)當(dāng)以功能
進(jìn)行模塊劃分。業(yè)務(wù)模塊
核心是業(yè)務(wù)饮怯,應(yīng)當(dāng)優(yōu)先以業(yè)務(wù)
進(jìn)行模塊劃分闰歪,其次再以功能
進(jìn)行模塊劃分。
1.3 Android如何做分層處理蓖墅?
前端開發(fā)其實(shí)就是做數(shù)據(jù)搬運(yùn)库倘,再展示到視圖中。數(shù)據(jù)
與視圖
是兩個(gè)不同的概念论矾,為了提高復(fù)用性以及可維護(hù)性教翩,我們應(yīng)當(dāng)根據(jù)單一設(shè)計(jì)原則
我們應(yīng)當(dāng)將二者進(jìn)行分層處理,所以無論是MVC
拇囊、MVP
還是MVVM
最核心的點(diǎn)都是將數(shù)據(jù)
與視圖
進(jìn)行分層迂曲。
絆腳石:
通常來講,我們通過網(wǎng)絡(luò)請(qǐng)求拿到數(shù)據(jù)結(jié)構(gòu)都是后端定義的寥袭,這也就意味著視圖層不得不直接使用后端定義的字段路捧,一旦后端進(jìn)行業(yè)務(wù)調(diào)整會(huì)迫使我們前端從
數(shù)據(jù)層-->視圖層
都會(huì)進(jìn)行對(duì)應(yīng)的改動(dòng),如下偽代碼所示:
//原始邏輯
數(shù)據(jù)層
Model{
title
}
UI層
View{
textView = model.title
}
//后端調(diào)整后
數(shù)據(jù)層
Model{
title
prefix
}
UI層
View{
textView = model.prefix + model.title
}
起初我們的textView
顯示的是model
中的title
传黄,但后端調(diào)整后我們需要在model
中加一個(gè)prefix
字段杰扫,同時(shí)textView
顯示內(nèi)容也要做一次字符串拼接。視圖層因?yàn)閿?shù)據(jù)層的改動(dòng)而被動(dòng)做了修改膘掰。既然做了分層我們想要的肯定是視圖章姓、數(shù)據(jù)互不干擾,如何解決识埋?往下看...
1.4 Data Mapper或許是解藥
Data Mapper
是后端常用的一個(gè)概念凡伊,一般情況下他們是不會(huì)直接使用數(shù)據(jù)庫里面的字段,而是加一個(gè)Data Mapper(數(shù)據(jù)映射)
將數(shù)據(jù)庫表轉(zhuǎn)按需換成Java Bean
,這樣做的好處也很明顯窒舟,表結(jié)構(gòu)甭管怎么折騰都不會(huì)影響到業(yè)務(wù)層代碼系忙。
對(duì)于前端我覺得可以適當(dāng)引入Data Mapper
,將后端數(shù)據(jù)轉(zhuǎn)換成本地模型
惠豺,本地模型只與設(shè)計(jì)圖對(duì)應(yīng)银还,將后端業(yè)務(wù)
與視圖
完全隔離风宁。這也就解決了 1.3 面臨的問題,具體方式如下:
數(shù)據(jù)層
Model{
title
prefix
}
本地模型(與設(shè)計(jì)圖一一對(duì)應(yīng))
LocalModel{
//將后端模型轉(zhuǎn)換為本地模型
title = model.prefix + model.title
}
UI層
View{
textView = localModel.title
}
LocalModel
相當(dāng)于一個(gè)中間層蛹疯,通過適配器模式
將數(shù)據(jù)層與視圖層做隔離戒财。
前端引入Data Mapper
后可以脫離后端進(jìn)行開發(fā),只要需求明確就可以做視圖
層的開發(fā)捺弦,完全不需要擔(dān)心后端返回什么結(jié)構(gòu)
饮寞、字段
。并且這種做法是一勞永逸的列吼,比如后端需要對(duì)某些字段做調(diào)整骂际,我們可以不暇思索直奔數(shù)據(jù)層
,涉及到的調(diào)整100%不會(huì)影響到視圖層
注意點(diǎn):
當(dāng)下有一部分公司為了將前后端分離更徹底冈欢,由前端開發(fā)人員提供
Java Bean(相當(dāng)于LocalModel)
的結(jié)構(gòu)歉铝,好處也很明顯,更多的業(yè)務(wù)內(nèi)聚到后端凑耻,很大程度提升了業(yè)務(wù)的靈活性太示,畢竟App發(fā)一次版成本還是比較大的。面對(duì)這種情況我們其實(shí)沒必要再編寫Data Mapper
香浩。所以任何架構(gòu)設(shè)計(jì)都要結(jié)合實(shí)際情況类缤,適合自己的才是最好的。
1.5 無處安放的業(yè)務(wù)邏輯
關(guān)于業(yè)務(wù)邏輯
其實(shí)是一個(gè)很籠統(tǒng)的概念邻吭,甚至可以將任意一行代碼稱之為業(yè)務(wù)邏輯
餐弱,如此寬泛的概念我們?cè)撊绾稳ダ斫猓课蚁却笾聦⑺譃閮蓚€(gè)方面:
- 界面交互邏輯:視圖層的交互邏輯囱晴,比如手勢(shì)控制膏蚓、吸頂懸浮等等都是根據(jù)業(yè)務(wù)需要實(shí)現(xiàn)的,所以嚴(yán)格來說這部分也屬于業(yè)務(wù)邏輯畸写。但這部分
業(yè)務(wù)邏輯
一般在視圖層實(shí)現(xiàn)驮瞧。- 數(shù)據(jù)邏輯:這部分是大家常說的業(yè)務(wù)邏輯,屬于強(qiáng)業(yè)務(wù)邏輯枯芬,比如根據(jù)不同用戶類型獲取不同數(shù)據(jù)论笔、展示不同界面,加上Data Mapper一系列操作其實(shí)就是給后端兜底千所,幫他們補(bǔ)全剩余邏輯而已狂魔。為了方便大家理解下文我將
數(shù)據(jù)邏輯
統(tǒng)稱為業(yè)務(wù)邏輯
。
前面我們說到淫痰,Android開發(fā)應(yīng)該具備數(shù)據(jù)層
跟視圖層
最楷,那業(yè)務(wù)邏輯放在哪一層比較合適呢?比如MVVM
模式下大家都說將業(yè)務(wù)邏輯
放到ViewModel
處理,這么說也沒有太大的問題管嬉,但如果一個(gè)界面足夠復(fù)雜那對(duì)應(yīng)的ViewModel
代碼可能會(huì)有成百上千行,看起來會(huì)很臃腫可讀性也非常差朗鸠。最重要的一點(diǎn)這些業(yè)務(wù)很難編寫單元測(cè)試用例
蚯撩。
關(guān)于業(yè)務(wù)邏輯我建議單獨(dú)寫一個(gè)use case
處理。
use case
通常放在ViewModel/Presenter
與數(shù)據(jù)層
之間烛占,業(yè)務(wù)邏輯以及Data Mapper
都應(yīng)該放在use case
中胎挎,每一個(gè)行為對(duì)應(yīng)一個(gè)use case
。這樣就解決了ViewModel/Presenter
臃腫的問題忆家,同時(shí)更方便編寫測(cè)試用例犹菇。
注意點(diǎn):
好的設(shè)計(jì)都是特定場(chǎng)景解決特定問題,過度設(shè)計(jì)不僅解決不了任何問題反而會(huì)增加開發(fā)成本芽卿。以我目前經(jīng)驗(yàn)來看Android開發(fā)至少一半的場(chǎng)景都很簡(jiǎn)單:
請(qǐng)求-->拿數(shù)據(jù)-->渲染視圖
最多再加個(gè)Data Mapper
揭芍,流程很單一并且后期改動(dòng)的可能也不太大,這種情況就沒必要寫一個(gè)use case卸例,Data Mapper
扔到數(shù)據(jù)層即可称杨。
2. 合理分層是給 數(shù)據(jù)驅(qū)動(dòng)UI 做鋪墊
先說結(jié)論:數(shù)據(jù)驅(qū)動(dòng)UI的本質(zhì)是控制反轉(zhuǎn)
2.1 什么是 控制反轉(zhuǎn)?
控制
即對(duì)程序流程的控制筷转,一般由我們開發(fā)者承擔(dān)姑原,此過程為控制
。但開發(fā)者是人所以不可避免出現(xiàn)錯(cuò)誤呜舒,此時(shí)可以將角色做一個(gè)反轉(zhuǎn)
由成熟的框架負(fù)責(zé)整個(gè)流程锭汛,程序員只需要在框架預(yù)留的擴(kuò)展點(diǎn)上,添加跟自己的業(yè)務(wù)代碼袭蝗,就可以利用框架來驅(qū)動(dòng)整個(gè)程序流程的執(zhí)行唤殴,此過程為反轉(zhuǎn)
。
控制反轉(zhuǎn)
概念和設(shè)計(jì)原則中的依賴倒置
很相似到腥,只是少了一個(gè)依賴抽象
眨八。
打個(gè)比方:
現(xiàn)有一個(gè)
HTTP請(qǐng)求
的需求,如果想自己維護(hù)HTTT鏈接
左电、自己管理TCP Socket
廉侧、自己處理HTTP緩存
.....就是整個(gè)HTTP協(xié)議
全部自己封裝,先不說這個(gè)工程能不能靠個(gè)人實(shí)現(xiàn)篓足,就算實(shí)現(xiàn)也是漏洞百出段誊,此時(shí)可以換個(gè)思路:通過OkHttp
去實(shí)現(xiàn),OkHttp
是一個(gè)成熟的框架用它基本上不會(huì)出錯(cuò)栈拖。個(gè)人封裝HTTP協(xié)議
到使用OkHttp框架
连舍,這個(gè)過程在控制
HTTP的角色上發(fā)生了一個(gè)反轉(zhuǎn)
,個(gè)人--->成熟的框架OkHttp
即控制反轉(zhuǎn)涩哟,好處也很明顯索赏,框架出錯(cuò)的概率遠(yuǎn)低于個(gè)人盼玄。
2.2 什么是數(shù)據(jù)驅(qū)動(dòng)UI?
通俗一點(diǎn)說就是當(dāng)數(shù)據(jù)改變時(shí)對(duì)應(yīng)的UI也要跟著變潜腻,反過來說當(dāng)需要改變UI只需要改變對(duì)應(yīng)的數(shù)據(jù)即可“6現(xiàn)在比較流行的UI框架如Flutter
、Compose
融涣、Vue
其本質(zhì)都是基于函數(shù)式編程實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI童番,它們共同的目的都是為了解決數(shù)據(jù),UI一致性問題威鹿。
在當(dāng)前的Android中可以使用DataBinding
實(shí)現(xiàn)同樣的效果剃斧,以Jetpack MVVM
為例:ViewModel
從Repository
拿到數(shù)據(jù)暫存到ViewModel
對(duì)應(yīng)的ObservableFiled
即可實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI,但前提是從Repository
拿到的數(shù)據(jù)可以直接用忽你,如果在Activity
或者Adapter
做數(shù)據(jù)二次處理再notify UI
幼东,已經(jīng)違背數(shù)據(jù)驅(qū)動(dòng)UI核心思想。所以想實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI必須要有合理的分層(UI層拿到的數(shù)據(jù)無需處理科雳,可以直接用)
筋粗,Data Mapper
恰好解決這一問題,同時(shí)也可規(guī)避大量編寫BindAdapter
的現(xiàn)狀炸渡。
DataBinding
并非函數(shù)式編程娜亿,它只是通過AbstractProcessor
生成中間代碼,將數(shù)據(jù)映射到XML中
2.3 為什么說數(shù)據(jù)驅(qū)動(dòng)UI底層思想是控制反轉(zhuǎn)蚌堵?
當(dāng)前Android生態(tài)能實(shí)現(xiàn)數(shù)據(jù)綁定UI的框架只有兩個(gè):DataBinding买决、Compose(暫不討論)
在引入DataBinding之前渲染一條數(shù)據(jù)通常需要兩步,如下:
var title = "iOS"
fun setTitle(){
//第一步更改數(shù)據(jù)源
title = "Android"
//第二個(gè)更改UI
textView = title
}
共需要兩步更改數(shù)據(jù)源吼畏、更改UI督赤,數(shù)據(jù)源
跟UI
有一個(gè)忘記修改便會(huì)出現(xiàn)BUG,千萬不要說:“兩個(gè)我都不會(huì)忘記修改
”泻蚊,當(dāng)面臨復(fù)雜的邏輯以及十幾個(gè)甚至幾十個(gè)的數(shù)據(jù)源很難保證不出錯(cuò)躲舌。這種問題可以通過DataBinding
解決,只需更改對(duì)應(yīng)的ObservableFiled
UI便會(huì)同步修改性雄,控制
UI狀態(tài)也從個(gè)人反轉(zhuǎn)
到的DataBinding
没卸,個(gè)人疏忽的事情DataBinding
可不會(huì)。
所以說數(shù)據(jù)驅(qū)動(dòng)UI底層思想是控制反轉(zhuǎn)
2.4 為什么引入Diff秒旋?
引入diff
之前:
RecyclerView
想要實(shí)現(xiàn)動(dòng)態(tài)刪除约计、添加、更新需要分別手動(dòng)更新數(shù)據(jù)和UI迁筛,這樣在中間插了一道
并且分別更新數(shù)據(jù)和UI已經(jīng)違背了前面所說的數(shù)據(jù)驅(qū)動(dòng)UI
煤蚌,而我們想要的是不管刪除、添加或者更新只有一個(gè)入口,只要改變數(shù)據(jù)源就會(huì)驅(qū)動(dòng)UI做更新尉桩,想要滿足這一原則只能改變數(shù)據(jù)源后對(duì)RecyclerView
做全部刷新筒占,但這樣會(huì)造成性能問題,復(fù)雜的界面會(huì)感到明顯的卡頓蜘犁。
引入diff
之后:
Diff
算法通過對(duì)oldItem
和newItem
做差異化比對(duì)翰苫,會(huì)自動(dòng)更新改變的item
,同時(shí)支持刪除沽瘦、添加的動(dòng)畫效果,這一特性解決了RecyclerView
需要實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI
的性能問題
3 為什么我建議使用 函數(shù)式編程
3.1 什么是 函數(shù)式編程农尖?
- 一個(gè)入口析恋,一個(gè)出口。
- 不在函數(shù)鏈內(nèi)部執(zhí)行與運(yùn)算本身無關(guān)的操作
- 不在函數(shù)鏈內(nèi)部使用外部變量(實(shí)際上這一條很難遵守盛卡,可以適當(dāng)突破)
說的通俗點(diǎn)就是給定一個(gè)初始值助隧,經(jīng)過函數(shù)鏈的運(yùn)行會(huì)得到一個(gè)目標(biāo)值,運(yùn)算的過程中外部沒有插手的權(quán)限滑沧,同時(shí)不做與本身無關(guān)的操作并村,從根本上解決了不可預(yù)期錯(cuò)誤的產(chǎn)生。
舉個(gè)例子:
//Kotlin代碼
listOf(10, 20).map {
it + 1
}.forEach {
Log.i("list", "$it")
}
上面這種鏈?zhǔn)骄幊叹褪菢?biāo)準(zhǔn)的函數(shù)式編程滓技,輸入到輸出之間開發(fā)者根本沒有插手的機(jī)會(huì)(即Log.i(..)
之前開發(fā)者沒有權(quán)限處理list)哩牍,所以整個(gè)流程是100%
安全的,RxJava
令漂、Flow
膝昆、鏈?zhǔn)礁唠A函數(shù)
都是標(biāo)準(zhǔn)的函數(shù)式編程,它們從規(guī)范
層面解決數(shù)據(jù)安全問題叠必。所以我建議在Kotlin
中 碰到數(shù)據(jù)處理盡量使用鏈?zhǔn)礁唠A函數(shù)(RxJava荚孵、Kotlin Flow亦然)
。
其實(shí)函數(shù)式編程的核心思想就是 門面模式 以及 迪米特法則
3.2 Android視圖開發(fā)可以借鑒函數(shù)式編程思想
Android視圖開發(fā)大都遵循如下流程:請(qǐng)求-->處理數(shù)據(jù)-->渲染UI纬朝,這一流程可以借鑒函數(shù)式編程收叶,將請(qǐng)求作為入口,渲染做為出口共苛,在這個(gè)流程中盡量不做與當(dāng)前行為無關(guān)的事(這也要求ViewModel
,Repository
中的函數(shù)要符合單一原則)判没。這樣說有點(diǎn)籠統(tǒng),下面舉個(gè)反例:
View{
//刷新
fun refresh(){
ViewModel.load(true)
}
//加載更多
fun loadMore(){
ViewModel.load(false)
}
}
ViewModel{
//加載數(shù)據(jù)
load(isRefresh){
if (isRefresh){
//刷新
}else{
//加載更多
}
}
}
View
層有刷新隅茎、加載更多兩種行為哆致,load(isRefresh)
一個(gè)入口,兩個(gè)出口患膛。面臨的問題很明顯摊阀,修改刷新
或加載更多
都會(huì)對(duì)對(duì)方產(chǎn)生影響,違反開閉原則
中的閉(對(duì)修改關(guān)閉:行為沒變不準(zhǔn)修改源代碼)
,導(dǎo)致存在不可預(yù)期的問題產(chǎn)生胞此〕伎В可以借鑒函數(shù)式編程
思想對(duì)其進(jìn)行改進(jìn),將ViewModel
的load
函數(shù)拆分成refresh
和loadMore
漱牵,這樣刷新
和加載更多
兩種行為夺蛇、兩個(gè)入口、兩個(gè)出口互不干涉酣胀,通過函數(shù)的銜接形成兩條獨(dú)立的業(yè)務(wù)鏈條刁赦。
函數(shù)式編程可以約束我們寫出規(guī)范的代碼,面對(duì)不能使用函數(shù)式編程的場(chǎng)景闻镶,我們可以嘗試自我約束往函數(shù)式編程方向靠攏甚脉,大致也能實(shí)現(xiàn)相同的效果。
綜上所述
- 合理的分層可以提升復(fù)用性铆农、降低模塊間耦合性
- Data Mapper 可以讓視圖層脫離于后端進(jìn)行開發(fā)
- 復(fù)雜的業(yè)務(wù)邏輯應(yīng)該寫到use case中
- 數(shù)據(jù)驅(qū)動(dòng)UI的本質(zhì)是控制反轉(zhuǎn)
- 通過函數(shù)式編程可以寫出更加安全的代碼
如果大家對(duì)Jetpack MVVM
感興趣歡迎留言牺氨,下篇文章我可以寫一下自己的看法..
【Android面試題】2022最新Android中高級(jí)大廠高頻面試題匯總助力金三銀四高薪必備_嗶哩嗶哩_bilibili
Android開發(fā)進(jìn)階學(xué)習(xí)—設(shè)計(jì)思想解讀開源框架 · 已更新至104集(持續(xù)更新中~)_嗶哩嗶哩_bilibili
Android音視頻開發(fā):音視頻基礎(chǔ)知識(shí)到直播推流實(shí)戰(zhàn)系列教程_嗶哩嗶哩_bilibili
Android項(xiàng)目實(shí)戰(zhàn)-從0開始手把手實(shí)現(xiàn)組件化路由SDK項(xiàng)目實(shí)戰(zhàn)_嗶哩嗶哩_bilibili