一、什么是 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 的代码进行阻隔,替换工作将有多么的庞大。