美文网首页
Kotlin by委托属性实战之 MMKV 封装

Kotlin by委托属性实战之 MMKV 封装

作者: Bfmall | 来源:发表于2023-08-05 20:48 被阅读0次

    一、什么是 Kotlin 属性委托

    提到属性委托之前,我们先来认识一下委托模式(delegation pattern),引用 WIKI 上的解释:

    委托模式(delegation pattern)是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。
    通俗来讲,委托就是将自己要做的事情,委托给其他人来做。

    Kotlin 的属性委托也是如此,将属性的 获取(get()) 与 赋值(set()) 委托给别的类来做。

    具体语法如下:

    val/var <属性名>: <类型> by <表达式>
    

    在 by 后面的表达式是该委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现接口,但是需要提供一个 getValue() 函数(对于 var 属性还有 setValue())
    举个例子:

    class Delegate {
    
        private var propertyDelegate: Int = 0
    
        private val offset = 100
    
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
            return propertyDelegate
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
            propertyDelegate = (value + offset)
        }
    }
    
    class DelegateTest {
    
        private var testProperty by Delegate()
    
        @Test
        fun test() {
            println(testProperty)  // 0
            testProperty = 100
            println(testProperty)  // 200
            testProperty = -100
            println(testProperty)  // 0
        }
    }
    

    在上面的例子中我们将属性 testProperty 委托给 Delegate 实现,我为了示例效果在 Delegate 中对属性增加了一个偏移量,每次赋值都会额外加上这个偏移量,实际测试的打印效果也符合预期。

    从这个例子中我们其实可以得到一些启发:

    我们可以干预属性的获取和赋值这两个行为,可以定制我们想要的一些行为。

    对于委托属性的介绍先到此为止,想了解关于委托属性更多的内容可以访问 Kotlin 的文档 属性委托

    二、使用 Kotlin 属性委托封装 MMKV

    使用预览
    我们先来看一下 MMKV 使用属性委托封装后如何使用

    var clickCount by MMKVDelegate("click_count", 0)
    
    var timestamp by MMKVDelegate("timestamp", 0L)
    
    var message by MMKVDelegate("message", "Hello World!")
    
    fun test() {
        clickCount += 1
        timestamp = System.currentTimeMillis()
        Log.d("qqq", "clickCount:$clickCount timestamp:$timestamp message:$message")
    
        clickCount = 0
        timestamp = 0L
        message = "clean"
        Log.d("qqq", "clickCount:$clickCount timestamp:$timestamp message:$message")
    }
    
    // 输出结果如下:
    // clickCount:1 timestamp:1688505455748 message:Hello World!
    // clickCount:0 timestamp:0 message:clean
    

    这样使用是不是简单了很多呢?就和操作普通属性一模一样,简直不要太简单。
    我们废话不多说直接上干货:

    封装思路

    1.MMKV 的数据获取及写入,需要调用其 encode()/decodeXxx()方法来进行,这其实就和属性的 get()/set() 方法如出一辙,所以我们可以将数据的获取及写入通过属性委托来完成。
    2.MMKV 支持的类型众多,重载方法也很多,所以我们还需要对数据获取和写入的模版代码进行一次封装处理,我这边只示例基本类型和 String。

    1.首先对数据获取及写入的模版代码封装,统一操作入口
    import com.tencent.mmkv.MMKV
    
    object MMKVUtils {
        
        private val mmkv = MMKV.defaultMMKV()
        
        fun <T> encode(key: String, value: T) {
            when (value) {
                is Int -> mmkv.encode(key, value)
                is Long -> mmkv.encode(key, value)
                is Float -> mmkv.encode(key, value)
                is Double -> mmkv.encode(key, value)
                is Boolean -> mmkv.encode(key, value)
                is String -> mmkv.encode(key, value)
            }
        }
        
        @Suppress("UNCHECKED_CAST")
        fun <T> decode(key: String, defValue: T): T {
            return when (defValue) {
                is Int -> mmkv.decodeInt(key, defValue) as T
                is Long -> mmkv.decodeLong(key, defValue) as T
                is Float -> mmkv.decodeFloat(key, defValue) as T
                is Double -> mmkv.decodeDouble(key, defValue) as T
                is Boolean -> mmkv.decodeBool(key, defValue) as T
                is String -> mmkv.decodeString(key, defValue) as T
                else -> defValue
            }
        }
    }
    

    上述代码,我们通过常用手段(泛型)来将数据获取及写入的模版代码统一到两个方法中,其实本来 MMKV.encode() 方法的重载就可以自动识别参数类型,但是 MMKV.decodeXxx() 必须要指定具体类型的方法。
    对于 MMKV 的实例可以根据我们自身项目的需求来进行构建,我这边为了方便直接使用了 defaultMMKV() 方法来构建了默认实例。

    2.使用属性委托来封装使用方法

    我们先来认识下 Kotlin 提供的两个类:

    kotlin.properties.ReadOnlyProperty
    kotlin.properties.ReadWriteProperty
    

    这两个类可以帮助我们实现只读/可读可写属性的委托,可以减少我们重复写一些模板代码,这两个类的源码如下:

    /**
     * Base interface that can be used for implementing property delegates of read-only properties.
     *
     * This is provided only for convenience; you don't have to extend this interface
     * as long as your property delegate has methods with the same signatures.
     *
     * @param T the type of object which owns the delegated property.
     * @param V the type of the property value.
     */
    public fun interface ReadOnlyProperty<in T, out V> {
        /**
         * Returns the value of the property for the given object.
         * @param thisRef the object for which the value is requested.
         * @param property the metadata for the property.
         * @return the property value.
         */
        public operator fun getValue(thisRef: T, property: KProperty<*>): V
    }
    
    
    /**
     * Base interface that can be used for implementing property delegates of read-write properties.
     *
     * This is provided only for convenience; you don't have to extend this interface
     * as long as your property delegate has methods with the same signatures.
     *
     * @param T the type of object which owns the delegated property.
     * @param V the type of the property value.
     */
    public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
        /**
         * Returns the value of the property for the given object.
         * @param thisRef the object for which the value is requested.
         * @param property the metadata for the property.
         * @return the property value.
         */
        public override operator fun getValue(thisRef: T, property: KProperty<*>): V
    
        /**
         * Sets the value of the property for the given object.
         * @param thisRef the object for which the value is requested.
         * @param property the metadata for the property.
         * @param value the value to set.
         */
        public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
    }
    

    由于我们的数据一般都是可读可写的,所以我们借助 ReadWriteProperty 类来实现我们的委托逻辑:

    import kotlin.properties.ReadWriteProperty
    
    class MMKVDelegate<T>(private val key: String, private val def: T) : ReadWriteProperty<Any?, T> {
    
        override fun getValue(thisRef: Any?, property: KProperty<*>): T = MMKVUtils.decode(key, def)
    
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =
            MMKVUtils.encode(key, value)
    }
    

    其实上面的代码也很简单,将属性的访问(get())重写为通过 MMKVUtils.decode() 方法来获取 MMKV 中指定 Key 的数据,将属性的赋值(set())重写为通过 MMKVUtils.encode() 方法来给指定 Key 设置值。
    其中我们在主构造中,需要传入 Key 和 默认值,这样我们就可以像操作正常属性一样来对 MMKV 中指定 Key 进行获取/赋值,同时还将 MMKV 的操作进行隔离,方便后期进行替换,如替换为 DataStore,我们就可以直接修改 MMKVDelegate 完成替换,试想一下,如果没有对 MMKV 的代码进行隔离,替换工作将有多么的庞大。

    作者:Quyunshuo
    链接:https://juejin.cn/post/7251876857704038458
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:Kotlin by委托属性实战之 MMKV 封装

          本文链接:https://www.haomeiwen.com/subject/tthipdtx.html