一、什么是 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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
网友评论