Kotlin 委托属性(Delegated properties)实战之 MMKV 封装

本文介绍了Kotlin中的属性委托概念,通过一个示例展示了如何自定义委托实现属性的获取和赋值行为。接着,文章演示了如何利用属性委托封装MMKV存储库,简化代码并提高可维护性,提供了MMKVUtils工具类和MMKVDelegate类的实现细节。
摘要由CSDN通过智能技术生成

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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值