Android 開發過程當中,Model 層一般是比較薄弱的。獲取數據的代碼通過各類優秀的封裝,已經能夠簡化到短短几行代碼,對於簡單的項目而言,全都寫在 Activity/Fragment 中就是最合適的了,若是使用了 MVP 或者 MVVM 模式,也基本會把數據的獲取放在 Presenter/ViewModel 中。(後面用業務邏輯層表示 Controller/Presenter/ViewModel)android
但 Model 層是很重要的,MVC,MVP,MVVM 甚至更復雜的架構模式,都須要 Model 層。最近爲了作接口數據格式的自動化測試,又對 Model 層的實現進行了一次學習,本文記錄了學習過程當中的一些問題以及我的理解。問題以下:git
說來慚愧,以前寫過的代碼都是在業務裏作網絡請求,處理回調數據。這種方法存在幾個問題:github
而獨立出 Model 層能夠解決上述問題,使代碼更易讀且便於擴展,還能添加單元測試提升軟件質量,對於 App 的長期發展有很大的好處。數據庫
終於到了寫代碼的時間,此次參考的依然是 googlesamples/android-architecture。爲了知足單元測試的需求,我將一個 demo 項目重構爲 MVP 模式了。緩存
Model 層內部還須要再分層,將不一樣來源(網絡和本地)的數據獲取代碼分開。代碼的結構大概這樣:bash
業務層須要的數據獲取定義成 DataSource 接口中的函數,固定參數和回調。具體實現爲 LocalDataSource 和 RemoteDataSource 等。Repository 實現 DataSource 並持有具體的一種或多種 DataSource,經過組合不一樣的 DataSource 實現獲取數據的功能。網絡
某 App 首頁須要經過網絡獲取一個列表數據來展現,爲了更好的用戶體驗,每次刷新的列表會緩存在本地。這樣在請求成功前就不是空白的頁面了。架構
interface ExDataSource {
interface LoadListCallback{
fun onSuccess(list: ArrayList<ListItemBean>)
fun onError(errorCode: Int, errorMsg: String)
}
fun loadList(page: Int, callback: LoadListCallback)
}
複製代碼
而後分別實現具體的數據獲取方法:框架
// 對數據處理的函數能夠以靜態方法的方式提到外面
class ExRemoteDataSource: ExDataSource{
override fun loadList(page: Int, callback: LoadListCallback){
// 發起網絡請求,解析返回數據,若是不能解析成ArrayList<ListItemBean>也回調失敗
// 具體代碼實現與網絡請求框架有關,此處不放代碼了
}
}
···
class ExLocalDataSource: ExDataSource{
override fun loadList(page: Int, callback: LoadListCallback){
// 從數據庫或者文件獲取緩存的數據
}
fun setCacheList(list: ArrayList<ListItemBean>){
// 更新緩存內容
}
}
複製代碼
最後在 Repository 中處理緩存邏輯:ide
//
class ExRepositiry(
private val remoteDataSource: ExRemoteDataSource,
private val localDataSource: ExLocalDataSource
): ExDataSource {
override fun loadList(page: Int, callback: LoadListCallback){
//具體如何使用緩存跟需求有關,這裏簡化寫一下
// 先加載本地數據作顯示
localDataSource.loadList(page, callback)
// 同時發起網絡請求
remoteDataSource.loadList(page, object: LoadListCallback{
override fun onSuccess(list: ArrayList<ListItemBean>){
// 成功後更新緩存,刷新頁面
localDataSource.setCacheList(list)
callback.onSuccess(list)
}
override fun onError(errorCode: Int, errorMsg: String){
callback.onError(errorCode, errorMsg)
}
})
}
}
複製代碼
業務層只須要建立 Repository 就能得到想要的數據了,對於錯誤的狀況,就詳細規劃 onError 的回調,再根據具體需求處理。
接口回調是沒法從根本上取代的,若是爲了代碼簡明,能夠建立幾個泛型接口模板來避免每一個請求對應一個接口。使用 Kotlin 的話能夠直接按成功和失敗傳入函數:
···
fun loadList(
page: Int,
onSuccess: (ArrayList<ListItemBean>) -> Unit,
onError: (errorCode: Int, errorMsg: String) -> Unit
)
···
複製代碼
隨着項目的發展,須要的數據會愈來愈多,都寫在同一個 Repository 中獲取數據會讓代碼的可讀性降低。應該按照業務將 Repository 模塊化,以適應將來項目的模塊化和組件化。(不須要分得太細碎,Repository 自己由多個獨立的數據獲取代碼構成,即便有不少行也能保證邏輯清晰)
算了一下內容,再寫下去就太長了。並且關於單元測試的部分還沒應用到項目中,不肯定還有沒有坑,最後這個問題下週單獨寫一篇吧。
Model 層能夠說是欠了好久的技術債了,最初以爲只是幾行代碼的網絡請求拆出來也沒有意義,隨着業務的發展,出現了不少須要緩存的頁面,就也把取本地的數據的代碼寫在業務邏輯中了。如今要保證複雜邏輯代碼的穩定性,想要添加單元測試,再回頭看代碼才明白已經走偏了太多。
代碼的架構應該是分層,而不是分塊。不少代碼中把一部分業務邏輯委託到一個 xxManager 去作,表面上彷佛單個文件中的代碼少了,但並不符合單一職責原則,實際上代碼的可讀性仍是很差,後續維護也依然麻煩。
從事 Android App 開發快 2 年了,我居然還沒寫過單元測試,實際上是很無奈的一件事。不寫測試的理由可能有不少,但寫單元測試的理由只有爲了更高的代碼質量。爲了讓代碼可以長期維護下去,解耦和單元測試都是很是重要的。
那麼你開始寫單元測試了嗎?