一文理解 Kotlin 的委托

Kotlin 的委托分为两种,一种是类委托,一种是属性委托,还有一个特殊的委托提供者(委托的委托)。下面会依次进行介绍。

类委托

类委托示例如下:

kotlin 复制代码
interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

它其实等价于:

kotlin 复制代码
class Derived(val b: Base): Base {
    override fun print() {
        b.print()
    }
}

可以看到类委托其实就是委托模式的语法糖,通过这个 by 关键字,就可以自动将接口里的方法委托给一个对象,从而可以帮我们省略很多接口方法适配的模板代码。

属性委托

属性委托示例如下。其中 Delegate 类的 setValuegetValue 方法是固定的写法,不可修改。当我们委托 var 可变变量时,setValuegetValue 方法都需要。当我们委托 val 不可变变量时,只需要 getValue 方法。

kotlin 复制代码
class Example {
    var p: String by Delegate()
}


class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return ""
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
           
    }
}

它可以近似等价于:

kotlin 复制代码
class Example {
    
    private val p_delegate = Delegate()
    var p: String
        set(value) {
            p_delegate.setValue(this, ::p, value)
        }
        get() {
            return p_delegate.getValue(this, ::p)
        }
    
}

可以看到,属性委托其实是对属性的 set、get 方法的委托 。其中 KProperty 是属性的元数据,例如此处是p 的元数据,使用反射可以获取这些数据。

在开发过程中,setValuegetValue 的固定的写法太麻烦了。Google 就提供了 ReadWriteProperty 和 ReadOnlyProperty 两个接口,你只要根据需要实现其中一个接口就可以了,代码示例如下:

kotlin 复制代码
//使用 ReadWriteProperty 委托 var 变量
class Delegate: ReadWriteProperty<Example, String> {
    override fun getValue(thisRef: Example, property: KProperty<*>): String {
        return ""
    }

    override fun setValue(thisRef: Example, property: KProperty<*>, value: String) {
        
    }
}

//使用 ReadOnlyProperty 委托 val 变量
class Delegate: ReadOnlyProperty<Example, String> {
    override fun getValue(thisRef: Example, property: KProperty<*>): String {
        return ""
    }
}

标准委托

大部分情况下,我们不需要自定义委托,而是使用标准委托。标准委托有四种,分别是:委托给另一个属性、懒加载委托、观察委托以及映射委托。

委托给另一个属性

一个属性可以把它的 getter 与 setter 委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可以使用。

kotlin 复制代码
class MyClass {
   var newName: Int = 0
   @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
   var oldName: Int by this::newName
}
fun main() {
   val myClass = MyClass()
   // 通知:'oldName: Int' is deprecated.
   // Use 'newName' instead
   myClass.oldName = 42
   println(myClass.newName) // 42
}

代码示例如上所示,属性委托给另一个属性的方式在处理兼容情况时非常有用。当你想要以一种向后兼容的方式重命名一个属性的时候,就可以引入一个新的属性、 使用 @Deprecated 注解来注解旧的属性、并委托其实现。

by lazy 懒加载委托

对于一些需要消耗计算机资源的操作,我们希望它在被访问的时候才去触发,从而避免不必要的资源开销,这就是懒加载。在 kotlin 中,我们使用 by lazy 懒加载委托就可以简单的实现懒加载的功能。代码示例如下:

kotlin 复制代码
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

lazy 的实现如下所示。可以看到,默认情况下 lazy 实现懒加载是线程安全的。其中 PUBLICATION 模式会调用多次初始化方法,但只有第一次的有效;NONE 模式会调用多次,且会改变常量的值为最后一次的值,他不是线程安全的。

kotlin 复制代码
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)


public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

Delegates 观察委托

我们可以使用 Delegates 观察委托,它有两种方法 observablevetoableobservable 会监听属性的变化,vetoable 会拦截赋值,并根据判断条件来决定要不要赋值。

kotlin 复制代码
    // -1 是初始值
    var data by Delegates.observable(-1) { property, oldValue, newValue ->
        //监听 data 的变化
        println("oldValue = $oldValue newValue = $newValue")
    }
    // 0 是初始值
    var size by Delegates.vetoable(0) { property, oldValue, newValue ->
        //设置更新条件,返回为 true 时才会赋新值
        newValue < 100
    }

by map 映射委托

映射委托用的场景比较少。它的功能主要是将 Map 中的 key-value 值委托出去,代码示例如下:

kotlin 复制代码
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

如果你想要委托 var 变量,这时需要 MutableMap 。

委托提供者(委托的委托)

当委托比较复杂时,可能需要根据需要创建不同的委托。这时候就需要使用委托提供者 ,或者叫做委托的委托。它和 setValuegetValue 方法一样有固定的写法。代码如下所示:

kotlin 复制代码
operator fun provideDelegate(thisRef: T, property: KProperty<*>): D

不过这个写法不用记住,它同样有对应的接口 PropertyDelegateProvider,只要实现它就好了。我们知道,在学校老师和学生在食堂吃饭的价格是不一样的,这时如果需要设计一个刷卡系统,就可以用到委托提供者 。示例如下,我们根据智能卡的 id 来判断当前用户的身份是老师还是学生,如果是老师则使用 TeacherConsumeDelegate 执行老师的代理扣费服务;反之则执行 StudentConsumeDelegate 学生的代理扣费服务。

kotlin 复制代码
class CardDelegateProvider: PropertyDelegateProvider<Card, ReadOnlyProperty<Card, IConsumeStrategy>> {
    override fun provideDelegate(
        thisRef: Card,
        property: KProperty<*>
    ): ReadOnlyProperty<Card, IConsumeStrategy> {
        
        val delegate = if(thisRef.id.startsWith("tech_")) {
            TeacherConsumeDelegate()
        } else {
            StudentConsumeDelegate()
        }
        return delegate
    }
}
相关推荐
沐怡旸1 分钟前
【翻译】scrcpy(3.3.3)命令使用文档
android
沐怡旸8 分钟前
【翻译】adb(Android Debug Bridge) 帮助文档
android
QING6189 分钟前
Kotlin 协程中Job和SupervisorJob —— 新手指南
android·kotlin·android jetpack
lichong95121 分钟前
android 使用 java 编写网络连通性检查
android·java·前端
Digitally25 分钟前
如何从iPhone切换到Android
android·ios·iphone
2501_9160074728 分钟前
苹果应用商店上架的系统逻辑,从产品开发到使用 开心上架 上架IPA 交付审核流程
android·ios·小程序·https·uni-app·iphone·webview
r***869841 分钟前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
android·前端·后端
fei201211061 小时前
(2.4.2) Kotlin从零开始
kotlin
e***75391 小时前
SQL Server 数据库迁移到 MySQL 的完整指南
android·数据库·mysql
向上_503582911 小时前
Android之kotlin学习
开发语言·学习·kotlin