dcache-android框架中的设计模式详解

引言:孤独的人喜欢深夜,多情的人喜欢黄昏。幸福的人喜欢阳光,伤心的人偏爱风雨。

众所周知,dcache-android是本人一行一行代码手写出来的Android数据缓存框架,写了好几年了,虽然不是每天写,但一直在持续优化中。先放个Github地址github.com/dora4/dcach... ,欢迎watch+star+fork+follow四连,这是对我的肯定也是给我持续优化的动力。

写本文的时候,dcache-android框架的最新版本为2.2.6版本,分析源代码不提版本那不就是误人子弟?虽然在框架设计暴露出的低层模块API的时候,就考虑到了关键代码的变动要给开发者时间消化。但是高层模块,也就是很多类都依赖的(如顶层接口设计)的变动,在不同版本还是变化很大的。

抽象工厂模式

在最新的版本更新中,加入了MMKV简单数据的Repository的支持,原先的BaseRepository的设计中有如下代码。

kt 复制代码
/**
 * 非集合数据缓存接口。
 */
protected lateinit var cacheHolder: CacheHolder<M>

/**
 * 集合数据缓存接口。
 */
protected lateinit var listCacheHolder: CacheHolder<MutableList<M>>

因为之前只考虑了数据库缓存,所以也没有使用工厂模式。但是随着架构逐渐变得复杂,这一类问题其实可以使用抽象工厂模式对代码进行扩展。抽象工厂简单的来说,是工厂方法模式的升级版。工厂方法模式的工厂一个工厂接口只创建一种产品,而抽象工厂的工厂接口,可以创建多种类型的产品。就好比,手机卡不仅能打电话,还能发短信、流量上网。现在你可以制定一个套餐,每种套餐每月有多少电话通话时长、可以发多少条短信、多少G流量。一种套餐就是一个抽象工厂的实现类。这样用一个词来形容,就是产品簇。组装电脑是不是也是一个产品簇?你可以使用因特尔的处理器、西部数据的硬盘、罗技的鼠标键盘、英伟达的显卡、华硕的主板。咳咳,我先声明一点,以上内容没有任何形式的商业合作。好了,言归正传。在你设计一个系统的时候,如果未来有几个模块是必须有的,而且可能会有不同的实现组合,你可以考虑使用抽象工厂模式。

那么BaseRepository就变成了这样。

kt 复制代码
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的源代码。

kt 复制代码
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跟普通用户的访问数据是不是有可能不一样,访问者模式也可以方便进行权限限制。你老板是不是可以访问你的薪资,财务是不是也可以访问你的薪资,你的领导是不是还有可能能访问你的薪资,但是你同事不行。设计模式来源于工业生产和生活,哈哈。下面看代码。

kt 复制代码
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中,也有使用到访问者模式。

kt 复制代码
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>)
}

在数据分页器中接受访问者的访问。这里有两种默认的访问者实现,一种直接分页,还有一种是随机分页。

kt 复制代码
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
    }
}
kt 复制代码
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。这样也遵循了最小知识原则。如果你对框架的缓存机制不感兴趣,你只需要自己实现访问者。然后框架给你所有的缓存数据,你自己处理就好了,不用再细读源码。

总结

设计模式只是为了设计出更好扩展的系统,并不是非得为了使用设计模式而使用设计模式,具体还要看业务,有没有这个使用必要。当然开源框架本来就是给别人用的,所以设计模式用得比较多。架构设计的精髓不在于硬套设计模式进行设计,而是你设计得足够多了以后,不去硬性使用设计模式,而设计模式无处不在。这样你的架构设计能力就达到了一个新的境界了,设计模式你已经能完全掌控了。它已经融入到了你的骨髓,不是吗?

相关推荐
编程洪同学1 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
bandaoyu1 小时前
【设计模式】装饰器模式(Decorator Pattern)
设计模式·装饰器模式
lijiachang0307182 小时前
设计模式(一):单例模式
c++·笔记·学习·程序人生·单例模式·设计模式·大学生
氤氲息3 小时前
Android 底部tab,使用recycleview实现
android
抓哇FullStack-Junior3 小时前
设计模式——适配器模式
java·设计模式·适配器模式
Clockwiseee3 小时前
PHP之伪协议
android·开发语言·php
小林爱4 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
silver6874 小时前
桥接模式详解
设计模式
小何开发5 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟5 小时前
Android Jetpack LiveData源码解析
android·android jetpack