引言:孤獨的人喜歡深夜,多情的人喜歡黃昏。幸福的人喜歡陽光,傷心的人偏愛風雨。
眾所周知,dcache-android是本人一行一行代碼手寫出來的Android數據緩存框架,寫了好幾年了,雖然不是每天寫,但一直在持續優化中。先放個Github地址https://github.com/dora4/dcache-android ,歡迎watch+star+fork+follow四連,這是對我的肯定也是給我持續優化的動力。
寫本文的時候,dcache-android框架的最新版本為2.2.6版本,分析源代碼不提版本那不就是誤人子弟?雖然在框架設計暴露出的低層模塊API的時候,就考慮到了關鍵代碼的變動要給開發者時間消化。但是高層模塊,也就是很多類都依賴的(如頂層接口設計)的變動,在不同版本還是變化很大的。
抽象工廠模式
在最新的版本更新中,加入了MMKV簡單數據的Repository的支持,原先的BaseRepository的設計中有如下代碼。
/*** 非集合數據緩存接口。*/
protected lateinit var cacheHolder: CacheHolder<M>/*** 集合數據緩存接口。*/
protected lateinit var listCacheHolder: CacheHolder<MutableList<M>>
因為之前只考慮了數據庫緩存,所以也沒有使用工廠模式。但是隨著架構逐漸變得復雜,這一類問題其實可以使用抽象工廠模式對代碼進行擴展。抽象工廠簡單的來說,是工廠方法模式的升級版。工廠方法模式的工廠一個工廠接口只創建一種產品,而抽象工廠的工廠接口,可以創建多種類型的產品。就好比,手機卡不僅能打電話,還能發短信、流量上網。現在你可以制定一個套餐,每種套餐每月有多少電話通話時長、可以發多少條短信、多少G流量。一種套餐就是一個抽象工廠的實現類。這樣用一個詞來形容,就是產品簇。組裝電腦是不是也是一個產品簇?你可以使用因特爾的處理器、西部數據的硬盤、羅技的鼠標鍵盤、英偉達的顯卡、華碩的主板。咳咳,我先聲明一點,以上內容沒有任何形式的商業合作。好了,言歸正傳。在你設計一個系統的時候,如果未來有幾個模塊是必須有的,而且可能會有不同的實現組合,你可以考慮使用抽象工廠模式。
那么BaseRepository就變成了這樣。
abstract class BaseRepository<M, F : CacheHolderFactory<M>>(val context: Context) : ViewModel(), IDataFetcher<M>, IListDataFetcher<M> {
}
然后通過數據庫工廠創建它的集合與非集合模式的CacheHolder,MMKV亦然,如果以后有了新的緩存類型,也可以通過同樣的方式擴展。
適配器模式
在dcache-android的源碼中,有個地方使用到了適配器模式,
dora.cache.data.adapter.Result
和dora.cache.data.adapter.ResultAdapter
。接下來看一下ResultAdapter的源代碼。
package dora.cache.data.adapterimport dora.http.DoraCallback/*** 將實現[dora.cache.data.adapter.Result]的REST API接口返回的model數據適配成框架需要的* [dora.http.DoraCallback]對象,用于[dora.cache.repository.BaseRepository]的onLoadFromNetwork()中。** @see ListResultAdapter*/
open class ResultAdapter<M, R : Result<M>>(private val callback: DoraCallback<M>) : DoraCallback<R>() {/*** 適配[dora.http.DoraCallback]成功的回調。*/override fun onSuccess(model: R) {model.getRealModel()?.let { callback.onSuccess(it) }}/*** 適配[dora.http.DoraCallback]失敗的回調。*/override fun onFailure(msg: String) {callback.onFailure(msg)}
}
因為后端REST API接口設計的差異,可能不會直接返回的頂層數據類就是我們要完整緩存的數據。通常會有code、msg以及data的設計。那我們默認肯定是要支持直接緩存頂層數據類的,那么問題來了,要緩存的數據類不在最外層,比如是頂層數據類的一個成員屬性。而且舊的接口也是不能刪除的,不可能說我新增一個功能,把舊的改掉了。而我系統的其他地方又是非常好的設計,不想改,然后要把新舊模塊都接入進來。有沒有一種辦法,在不改變原有系統設計和不放棄舊接口的情況下,讓新接口也能夠接入到原有系統設計上?肯定是有的。排插就是最好的例子。墻上的插口和我電器的接口不一致,重新裝修的成本又太高,那怎么辦?買一個排插,新舊的電器都可以正常使用了。
訪問者模式
訪問者模式可能有些開發者用得不是很多,它是一個保證不破壞數據原有結構的情況下,對樣本數據進行抽樣訪問的一個設計模式。VIP跟普通用戶的訪問數據是不是有可能不一樣,訪問者模式也可以方便進行權限限制。你老板是不是可以訪問你的薪資,財務是不是也可以訪問你的薪資,你的領導是不是還有可能能訪問你的薪資,但是你同事不行。設計模式來源于工業生產和生活,哈哈。下面看代碼。
package dora.cache.data.visitorimport dora.cache.data.page.IDataPager/*** 分頁數據的訪問者,不破壞數據的原有結構,訪問數據。** @param <M>*/
interface IPageDataVisitor<M> {/*** 訪問數據分頁器。** @param pager*/fun visitDataPager(pager: IDataPager<M>)/*** 過濾出符合要求的一頁數據。** @param model 樣本數據* @param totalCount 數據總條數* @param currentPage 當前第幾頁* @param pageSize 每頁數據條數* @return 該頁的數據*/fun filterPageData(models: MutableList<M>, totalCount: Int, currentPage: Int, pageSize: Int): MutableList<M>
}
在dcache-android中,也有使用到訪問者模式。
package dora.cache.data.pageimport dora.cache.data.visitor.IPageDataVisitor/*** 數據分頁器,使用訪問者進行訪問。** @see IPageDataVisitor*/
interface IDataPager<M> {/*** 設置當前是第幾頁,建議從0開始。*/var currentPage: Int/*** 每頁有幾條數據?** @return 不要返回0,0不能做除數*/var pageSize: Intval models: MutableList<M>/*** 加載過濾后的頁面數據。*/fun loadData(models: MutableList<M>)/*** 頁面數據改變后,會回調它。*/fun onResult(result: (models: MutableList<M>) -> Unit) : IDataPager<M>/*** 接收具體訪問者的訪問,不同的訪問者將會以不同的規則呈現頁面數據。*/fun accept(visitor: IPageDataVisitor<M>)
}
在數據分頁器中接受訪問者的訪問。這里有兩種默認的訪問者實現,一種直接分頁,還有一種是隨機分頁。
package dora.cache.data.visitor/*** 默認的數據分頁器。*/
class DefaultPageDataVisitor<M> : BasePageDataVisitor<M>() {override fun filterPageData(models: MutableList<M>, totalCount: Int, currentPage: Int, pageSize: Int): MutableList<M> {val result: MutableList<M> = arrayListOf()val pageCount = if (totalCount % pageSize == 0) totalCount / pageSize else totalCount / pageSize + 1for (i in 0 until pageCount) {result.add(models[currentPage * pageSize + i])}return result}
}
package dora.cache.data.visitorimport kotlin.random.Random/*** 從樣本數據中隨機讀取數據的數據分頁器,不保證去重。*/
class RandomPageDataVisitor<M> : BasePageDataVisitor<M>() {override fun filterPageData(models: MutableList<M>, totalCount: Int, currentPage: Int, pageSize: Int): MutableList<M> {val result: MutableList<M> = arrayListOf()val pageCount = if (totalCount % pageSize == 0) totalCount / pageSize else totalCount / pageSize + 1for (i in 0 until pageCount) {result.add(models[Random.nextInt(totalCount)])}return result}
}
你也可以根據實際的業務需求,來擴展自己的訪問者。因為我不想你讀取數據還要改我dcache-android框架的代碼,所以設計了此訪問者結構。數據分頁器綁定onResult回調,一旦調用accept方法,接受某個訪問者的訪問,數據就會自動回調到onResult。這樣也遵循了最小知識原則。如果你對框架的緩存機制不感興趣,你只需要自己實現訪問者。然后框架給你所有的緩存數據,你自己處理就好了,不用再細讀源碼。
總結
設計模式只是為了設計出更好擴展的系統,并不是非得為了使用設計模式而使用設計模式,具體還要看業務,有沒有這個使用必要。當然開源框架本來就是給別人用的,所以設計模式用得比較多。架構設計的精髓不在于硬套設計模式進行設計,而是你設計得足夠多了以后,不去硬性使用設計模式,而設計模式無處不在。這樣你的架構設計能力就達到了一個新的境界了,設計模式你已經能完全掌控了。它已經融入到了你的骨髓,不是嗎?