上篇《浅谈委托属性》对委托属性作了基本介绍以及它在标准库中应用的举例,今天继续,来谈谈构造属性的委托的基本要点。
属性委托的定义
属性委托分为两类:只读的,读写的。对于只读属性(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 函数里来的。
小结
今天主要是属性委托的进一步探讨,通过分析定义一个属性委托的必要条件,来深入理解了标准库里的几个委托定义接口。从写几个例子就能体会到,别看委托简单,只要用对地方用合适了,才能发挥它的作用。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓