浅谈委托属性 2:必要条件和定义

上篇《浅谈委托属性》对委托属性作了基本介绍以及它在标准库中应用的举例,今天继续,来谈谈构造属性的委托的基本要点。

属性委托的定义

属性委托分为两类:只读的,读写的。对于只读属性(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.")
    }
}

getValuesetValue 这两个方法,都是 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 中定义的字符串。

属性委托接口

「只读」和「读写」

标准库里,直接为我们提供了 valvar 类型的委托接口,分别是 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官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值