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++
相关推荐
不爱学习的小枫33 分钟前
scala的集合
开发语言·scala
梦醒沉醉33 分钟前
Scala的初步使用
开发语言·后端·scala
小白学大数据38 分钟前
Fuel 爬虫:Scala 中的图片数据采集与分析
开发语言·爬虫·scala
贩卖纯净水.1 小时前
《React 属性与状态江湖:从验证到表单受控的实战探险》
开发语言·前端·javascript·react.js
JouJz1 小时前
Java基础系列:深入解析反射机制与代理模式及避坑指南
java·开发语言·代理模式
白羊不吃白菜1 小时前
PAT乙级(1101 B是A的多少倍)C语言解析
c语言·开发语言
一号言安1 小时前
牛客python蓝桥杯11-32(自用)
开发语言·python
鸽鸽程序猿1 小时前
【JavaEE】SpringIoC与SpringDI
java·开发语言·java-ee
前期后期1 小时前
Android Compose是如何使用什么架构,多个Activity?还是Fragment?compose的ui又是如何卸载和挂载的呢?
android·ui·架构·kotlin
maybe啊2 小时前
js 使用 Web Workers 来实现一个精确的倒计时,即使ios手机锁屏或页面进入后台,倒计时也不会暂停。
开发语言·前端·javascript