导言:孤单的人喜爱深夜,多情的人喜爱傍晚。美好的人喜爱阳光,悲伤的人偏心风雨。

众所周知,dcache-android是本人一行一行代码手写出来的Android数据缓存结构,写了好几年了,尽管不是每天写,但一直在继续优化中。先放个Github地址github.com/dora4/dcach… ,欢迎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.Resultdora.cache.data.adapter.ResultAdapter。接下来看一下ResultAdapter的源代码。

package dora.cache.data.adapter
import 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.visitor
import 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.page
import dora.cache.data.visitor.IPageDataVisitor
/**
 * 数据分页器,运用拜访者进行拜访。
 *
 * @see IPageDataVisitor
 */
interface IDataPager<M> {
    /**
     * 设置当时是第几页,主张从0开始。
     */
    var currentPage: Int
    /**
     * 每页有几条数据?
     *
     * @return 不要回来0,0不能做除数
     */
    var pageSize: Int
    val 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 + 1
        for (i in 0 until pageCount) {
            result.add(models[currentPage * pageSize + i])
        }
        return result
    }
}
package dora.cache.data.visitor
import 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 + 1
        for (i in 0 until pageCount) {
            result.add(models[Random.nextInt(totalCount)])
        }
        return result
    }
}

你也能够根据实际的事务需求,来扩展自己的拜访者。由于我不想你读取数据还要改我dcache-android结构的代码,所以规划了此拜访者结构。数据分页器绑定onResult回调,一旦调用accept办法,承受某个拜访者的拜访,数据就会自动回调到onResult。这样也遵从了最小知识准则。假如你对结构的缓存机制不感兴趣,你只需要自己完成拜访者。然后结构给你一切的缓存数据,你自己处理就好了,不用再细读源码。

总结

规划方式只是为了规划出更好扩展的体系,并不是非得为了运用规划方式而运用规划方式,详细还要看事务,有没有这个运用必要。当然开源结构原本便是给别人用的,所以规划方式用得比较多。架构规划的精髓不在于硬套规划方式进行规划,而是你规划得足够多了今后,不去硬性运用规划方式,而规划方式无处不在。这样你的架构规划能力就达到了一个新的境界了,规划方式你现已能彻底掌控了。它现已融入到了你的骨髓,不是吗?