携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情。
上篇《浅谈委托属性》对委托属性作了基本介绍以及它在标准库中应用的举例,今天继续,来谈谈构造属性的委托的基本要点。
属性委托的定义
属性委托分为两类:只读的,读写的。对于只读属性(val 类型),委托的定义应该长这样:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
}
而如果是读写属性(var 类型),那还应该额外加上 setValue 方法:
class Delegate {
operator fun getValue() /* ... */
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
getValue 和 setValue 这两个方法,都是 operator 重载,其实就是读值与写值的操作,方法参数包括:thisRef, property, 和 set 中的 value,它们分别代表什么呢?
首先,thisRef 是指委托属性的持有者的引用,其类型,必须与持有者相同或者是持有者的父类型;property 即为此委托属性的属性信息(KProperty表示);而 setValue 方法还有一个参数 value,是指要设置的值,其类型自然就是属性类型了。
回头再来看前面的例子,thisRef 的类型写成了 Any,其实就相当于用超类指代了,如果写成具体的持有类,当然更准确。
把上篇文章的 desc 挪到一个持有类:
class Owner {
var desc: String by Delegate()
}
fun main(args: Array<String>) {
val owner = Owner()
println("owner : $owner")
owner.desc = "test"
println(owner.desc)
}
看看日志:
owner : Owner@4883b407
test has been assigned to 'desc' in Owner@4883b407.
Owner@4883b407, thank you for delegating 'desc' to me!
日志说明,thisRef 和 owner 对象相同,就是持有者本尊了。那何为「委托属性持有者」呢?其实就是获取此属性时所在的对象。在上述代码中,修改参数类型为持有者,也是没有问题的:
class Delegate {
// thisRef 类型换成 Owner
operator fun getValue(thisRef: Owner?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
// thisRef 类型换成 Owner
operator fun setValue(thisRef: Owner?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
目前讲的都是属性委托的普通定义方式,其实还可以采用扩展方法来实现,这对于一些没有提供委托方法的 final 类,就显得十分有用了 —— 可以利用扩展,实现特定属性到此类型对象的委托。来看下面的例子:
class Bonjour
fun main(args: Array<String>) {
val bonjour = Bonjour()
println(bonjour) // Bonjour@c038203
}
输出:
Bonjour@c038203
Bonjour 是个普通的 final 类,无法被继承,它的对象只能采用「直接赋值」方式传给变量 bonjour,直接打印,打印的自然就是对象的哈希值了。因为无法被继承,我们通过扩展,赋予 Bonjour 委托功能:
operator fun Bonjour.getValue(thisRef: Any?, property: KProperty<*>): String {
return "Bonjour to you! [${property.name}]!"
}
通过 Bonjour 委托,可以得到一个获取 String 类型值的 getter:
fun main(args: Array<String>) {
val bonjour = Bonjour()
val bonjour2 by Bonjour()
println("$bonjour, $bonjour2")
}
输出:
Bonjour@d8355a8, Bonjour to you! [bonjour2]!
采用 by 形式的 bonjour2,调用了委托,输出了扩展函数 Bonjour.getValue 中定义的字符串。
属性委托接口
「只读」和「读写」
标准库里,直接为我们提供了 val 和 var 类型的委托接口,分别是 ReadOnlyProperty(只读属性委托) 和 ReadWriteProperty(读写属性委托):
public fun interface ReadOnlyProperty<in T, out V> {
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
都很很简单,和前面讲的定义形式一模一样,只是呢,这两个接口帮忙实现了泛型化,将持有者类型和委托类型进行了确切的指定 —— 「确切指定」,即类型是确定的,比如 ReadOnlyProperty 的委托使用,一定是在持有者 T 中发起的。下面实现一个只读委托,其持有类型是 Owner:
// Owner的只读委托
class OwnerDelegate: ReadOnlyProperty<Owner, String> {
override fun getValue(thisRef: Owner, property: KProperty<*>): String = "[$thisRef]"
}
使用它:
class Owner {
val del by OwnerDelegate()
}
fun main(args: Array<String>) {
val owner = Owner()
println(owner.del) // [Owner@5442a311]
}
可以看到,OwnerDelegate 是在 Owner 中使用的,如果直接放 main() 函数中,会报错:
fun main(args: Array<String>) {
/* 报错:Property delegate must have a 'getValue(Nothing?, KProperty*>)' method. None of the following functions are suitable.
*/
// val del by OwnerDelegate()
}
为什么用不了呢?因为委托指明了需要 Owner 类型持有,而这里根本没有 Owner 对象可用啊!
ReadWriteProperty 的使用也是类似,无非就是多了个 set 函数而已。
委托提供者
public fun interface PropertyDelegateProvider<in T, out D> {
public operator fun provideDelegate(thisRef: T, property: KProperty<*>): D
}
委托提供者,嗯,很「清格丽席」般地描述了这里的主角 PropertyDelegateProvider,当然,它的功能也就很明显了 —— 提供属性委托。其核心作用就是:在属性和委托之间建立一座「桥」,这样一来,在「桥」上,你可以额外添加一点工作,并且桥本身通向哪儿,你都是可以控制的。
既然「桥」都可以控制去哪儿,就意味着,我们可以定义动态变化的属性委托,不同的场景提供不同的委托!
来,继续延用前面的的例子:
val provider = PropertyDelegateProvider { thisRef: Any?, property: KProperty<*> ->
if (thisRef == null) {
ReadOnlyProperty<Any?, String> { thisRef, property ->
"not supported"
}
} else {
ReadOnlyProperty<Any?, Int> { thisRef, property ->
0
}
}
}
这里 provider 是 PropertyDelegateProvider 的匿名实对象,它提供了一个只读委托,但是呢,这个委托是根据实现场景而确定的:如果普通调用,那就返回 String 类型的委托;如果是特定对象内调用,那就返回 Int 类型的委托。使用如下:
class Owner {
val value by provider
}
fun main(args: Array<String>) {
val owner = Owner()
println(owner.value)
val value by provider
println(value)
}
结果:
0
not supported
结果如预期,同样的 provider,不同调用,得到的是不同的委托:第一个委托结果是 Int 型,因为由 Owner 对象得到;第二个委托结果是 String,因为它直接从 main 函数里来的。
小结
今天主要是属性委托的进一步探讨,通过分析定义一个属性委托的必要条件,来深入理解了标准库里的几个委托定义接口。从写几个例子就能体会到,别看委托简单,只要用对地方用合适了,才能发挥它的作用。