kotlin 委托

一、类委托

Kotlin 复制代码
interface DB{
    fun insert()
}
class SqliteDB : DB {
    override fun insert() {
        println(" SqliteDB insert")
    }
}

class MySql : DB{
    override fun insert() {
        println(" MySql insert")
    }
}

class OracleDB : DB{
    override fun insert() {
        println(" OracleDB insert")
    }
}

class CreateDB(db:DB) : DB by db

fun main() {
    val db = CreateDB(MySql())
    db.insert()
}

运行main()方法:

CreateDB类需要实现的接口(DB)方法委托给了具体实现类(MySql、OracleDB、SqliteDB)来实现,我认为这和设计模式中的策略模式如出一辙。

通过AndroidStudio的自带工具

Tools -> kotlin -> show kotlin Bytecode ,然后点Decompile,将字节码进行反编译得到java代码

也能佐证我们的想法:

二、委托属性

Kotlin 复制代码
private var floatValue = 789.12
private var num by ::floatValue

fun main() {
    println("num:$num")
    num = 567.88
    println("floatValue:$floatValue")
}

运行结果:

这个示例中将属性num 委托给了属性floatValue ,即对num 进行get和set操作其实都作用在了floatValue上,因此会有上面的运行结果。

同样我们可以看下反编译后的代码:

让我们稍感惊讶的是,经过转换后甚至都没有定义num这个属性,只是增加了getNum和setNum两个方法来获取和改变floatValue的值。

三、自定义委托

方式1:

Kotlin 复制代码
class Test3{
    var text:String by StringDelegate()
}

class StringDelegate{
    operator fun getValue(owner:Any,property:KProperty<*>):String{
        return "delegate value"
    }

    operator fun setValue(owner:Any,property:KProperty<*>,value:String){
        println("value:$value")
    }
}

fun main() {
    val t = Test3()
    println("text:${t.text}")
    t.text = "haha"
}

要点:

  1. 想要成为可接受委托的类需要实现运算符重载方法,若委托类实现了getValue和setValue(即可读可写),则被委托属性text可用var修饰;若委托类只实现了getValue(即只读),则被委托属性text可用val修饰。
  2. getValue的返回值,和setValue的value参数值类型要与被委托属性类型一致。
  3. getValue和setValue中的owner参数必须是被委托属性text所属的类或其父类。

方式2:

Kotlin 复制代码
class Test3{
    var text:String by StringDelegate()
    var text2:String by StringDelegate2()
}

class StringDelegate{
    operator fun getValue(owner:Any,property:KProperty<*>):String{
        return "delegate value"
    }

    operator fun setValue(owner:Any,property:KProperty<*>,value:String){
        println("value:$value")
    }
}

class StringDelegate2:ReadWriteProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "delegate value2"
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("value2:$value")
    }

}

fun main() {
    val t = Test3()
    println("text:${t.text}")
    t.text = "haha"

    println("text2:${t.text2}")
    t.text2 = "haha"
}

要点:

  1. 这种方式的原理就是方式一,只不过是系统Api给我们提供了一种相对方便的使用方式。
  2. 此方式需要出入两个泛型,第一个必须是被委托属性text所属的类或其父类,第二个是被委托属性的类型
  3. 若委托类支持可读可写则继承ReadWriteProperty,若只支持可读则继承ReadOnlyProperty

此方式原理:

方式3:

在上面的基础之上还有另一种方式,可以根据属性的一些特征来返回一个合适的委托实现类,有点类似于抽象工厂模式,根据特定的需求返回合适的实现,如下:

Kotlin 复制代码
class Test3{
    var text:String by StringDelegate()
    var text2:String by StringDelegate2()
    var text3:String by StringDelegateProvide()
}

class StringDelegate{
    operator fun getValue(owner:Any,property:KProperty<*>):String{
        return "delegate value"
    }

    operator fun setValue(owner:Any,property:KProperty<*>,value:String){
        println("value:$value")
    }
}

class StringDelegate2:ReadWriteProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "delegate value2"
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("value2:$value")
    }

}

class StringDelegate3:ReadWriteProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "delegate value3"
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("value3:$value")
    }

}


class StringDelegateProvide{
    operator fun provideDelegate(thisRef: Any,property: KProperty<*>):ReadWriteProperty<Any,String>{
        return if (property.name.contains("text3")){
            StringDelegate3()
        }else{
            StringDelegate2()
        }
    }
}

fun main() {
    val t = Test3()
    println("text:${t.text}")
    t.text = "haha"

    println("text2:${t.text2}")
    t.text2 = "haha"

    println("text3:${t.text3}")
}

四、使用案例

通过委托封装Mmkv存取值的过程

Kotlin 复制代码
import com.blankj.utilcode.util.GsonUtils
import com.tencent.mmkv.MMKV
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class MMKVDelegate<T>(
    private val mmkv: MMKV,
    private val cls: Class<T>,
    private val key: String,
    private val defaultValue: T
) : ReadWriteProperty<Any?, T> {

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return when {
            cls.isEnum -> {
                val enumValues = cls.enumConstants
                val enumValue = mmkv.decodeString(key, null)
                enumValues?.firstOrNull { it.toString() == enumValue } ?: defaultValue
            }

            cls.isData || MMKVDelegatedClass::class.java.isAssignableFrom(cls) -> runCatching {
                GsonUtils.fromJson(
                    mmkv.decodeString(
                        key
                    ), cls
                )
            }.getOrNull() ?: defaultValue

            cls.isAssignableFrom(String::class.java) -> mmkv.decodeString(
                key, defaultValue as String
            ) as T

            cls.isAssignableFrom(Int::class.java) -> mmkv.decodeInt(
                key, defaultValue as Int
            ) as T

            cls.isAssignableFrom(Boolean::class.java) -> mmkv.decodeBool(
                key, defaultValue as Boolean
            ) as T

            cls.isAssignableFrom(Float::class.java) -> mmkv.decodeFloat(
                key, defaultValue as Float
            ) as T

            cls.isAssignableFrom(Long::class.java) -> mmkv.decodeLong(
                key, defaultValue as Long
            ) as T

            cls.isAssignableFrom(Double::class.java) -> mmkv.decodeDouble(
                key, defaultValue as Double
            ) as T

            cls.isAssignableFrom(ByteArray::class.java) -> mmkv.decodeBytes(
                key, defaultValue as ByteArray
            ) as T

            else -> throw IllegalArgumentException("Unsupported type.")
        }
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val ok = when {
            cls.isEnum -> mmkv.encode(key, value?.toString())

            cls.isData || MMKVDelegatedClass::class.java.isAssignableFrom(cls) -> mmkv.encode(
                key,
                GsonUtils.toJson(value)
            )

            cls.isAssignableFrom(String::class.java) -> mmkv.encode(key, value as String)
            cls.isAssignableFrom(Int::class.java) -> mmkv.encode(key, value as Int)
            cls.isAssignableFrom(Boolean::class.java) -> mmkv.encode(key, value as Boolean)
            cls.isAssignableFrom(Float::class.java) -> mmkv.encode(key, value as Float)
            cls.isAssignableFrom(Long::class.java) -> mmkv.encode(key, value as Long)
            cls.isAssignableFrom(Double::class.java) -> mmkv.encode(key, value as Double)
            cls.isAssignableFrom(ByteArray::class.java) -> mmkv.encode(key, value as ByteArray)
            else -> throw IllegalArgumentException("Unsupported type.")
        }
        if (!ok) {
            throw MMKVDelegateException.EncodeException()
        }
    }
}
Kotlin 复制代码
import kotlin.jvm.internal.Reflection

val <T> Class<T>.isData: Boolean get() = Reflection.getOrCreateKotlinClass(this).isData

假设我们在项目中需要在本地记录一些App行为信息

Kotlin 复制代码
object AppBehavior {
    private const val TAG = "AppBehavior"
    private const val MMKV_ID = "AppBehavior"

    //记录App启动次数
    var appStartTime: Int by SettingValue(
        Int::class.java,
        Key.APP_START_TIME,
        0
    )

    /**
     * APP行为的属性委托
     * 值存储成功时,自动发送UserLocalSettingChangedEvent事件
     *  @param cls 属性的类型
     *  @param key 存储到MMKV的key
     *  @param defaultValue 从MMKV获取失败时,返回的默认值
     */
    private class SettingValue<T>(
        private val cls: Class<T>,
        private val key: Key,
        private val defaultValue: T
    ) : MMKVDelegate<T>(MMKV.mmkvWithID(MMKV_ID), cls, key.name, defaultValue) {
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            runCatching {
                super.setValue(thisRef, property, value)
            }.onFailure {
                Log.e(TAG, it.toString())
                if (it !is MMKVDelegateException) {
                    throw it
                }
            }
        }
    }
}

调用:

Kotlin 复制代码
AppBehavior.appStartTime++
相关推荐
2401_8574396932 分钟前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna1 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_1 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar2 小时前
速通Python 第三节
开发语言·python
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
信号处理学渣3 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客3 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
jasmine s4 小时前
Pandas
开发语言·python
biomooc4 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言