我正在参加「掘金·启航计划」
前言
本文是kotlin查漏补缺系列的第4篇,关于kotlin委托的分享,耐心看完本文你将学会
- kotlin的委托是什么?
- 类委托和属性委托分别是什么?怎么用?
什么是委托
委托也可叫代理,是一种常用的设计模式,具体表现为将对象的某些行为委托给其他的类来实现,打个比方,我想把房东的房子卖了,那我可以委托给房屋中介去帮我销售,这就是一种委托,其中我自己是委托对象,而中介是被委托对象
在kotlin中,委托分为类委托和属性委托,通过关键字by来实现
类委托
类委托指的是在类的层面进行委托代理,假设类A和类B都实现了同一个接口,接口定义了一个方法methodA,通过这里的类委托就能够把类A委托给类B,当我们调用类A对象的methodA方法时,内部实际会去执行类B中methodA方法的逻辑,有点儿绕?看栗子
还是来看卖房子的例子,我们可以先定义一个ISaleHouse接口接口,里面有一个函数表示卖房子
interface ISaleHouse {
// 卖房子
fun sale()
}
定义房屋中介类,实现ISaleHouse接口,这个类将作为被委托类
class DelegateAgent:ISaleHouse {
override fun sale() {
println("delegate sale")
}
}
再定义房屋所有者,同样实现ISaleHouse接口,这个类将作为委托类
class HouseOwner(agent:ISaleHouse):ISaleHouse by agent
注意到这个类的构造方法接收了一个ISaleHouse接口的对象,并且通过by关键字把这个类委托给了传进来的这个对象
这样就已经实现了kotlin的类委托,很简单吧,直接一个by关键字就解决,不用再像java一样,还需要在委托类中手动去调用被委托对象的相应方法
最后来验证一下
fun main(){
val agent = DelegateAgent()
val owner = HouseOwner(agent)
owner.sale()
}
// 运行结果
delegate sale
demo中把HouseOwner的对象Owner委托给了DelegateAgent对象agent,调用owner的sale方法时,运行结果打印的是被委托类DlegateAgent中sale方法的代码,委托完成
属性委托
看了上面的类委托的例子,不难看出委托的本质:把一个对象的某个函数委托给另外一个对象的相应函数去处理
那么属性委托也不例外,其实质也是对函数进行委托,那属性有什么函数?get和set
所以kotlin的属性委托,就是把属性的get和set方法委托给被委托对象,而被委托对象需要提供两个方法,setValue和getValue,对应的get方法将会委托给getValue,set方法委托给setValue,如果属性是val修饰的,只需要有getValue就够了
class DelegateProp{
operator fun getValue(thisRef:Any?,prop:KProperty<*>):String{
return "getValue ref is $thisRef,prop is ${prop.name}"
}
operator fun setValue(thisRef:Any?,prop:KProperty<*>,value:String){
println("setValue ref is $thisRef,prop is ${prop.name}")
}
}
class TestProp{
var myProp:String by DelegateProp()
}
fun main(){
val testProp = TestProp()
testProp.myProp = "1"
val a = testProp.myProp
}
demo中定义了DelegateProp类,提供了getValue和setValue两个函数,注意这两个函数必须被operator修饰的
然后把属性myProp通过by关键字委托给了DelegateProp对象,这样就实现了属性委托
在getValue和setValue两个函数两个函数中,有两个关键参数,thisRef和prop
- thisRef:必须是属性所在类型或者其超类
- prop:必须是KProperty类型或者其超类
如果每次写都需要像demo中这样一个个手敲多少有点不讲武德,不过kotlin肯定考虑到了,在标准库中给我们提供了两个接口,ReadOnlyProperty和ReadWriteProperty,如果属性是val修饰的,那么被委托类只需要实现ReadOnlyProperty接口就可以,如果是var修饰的,则要去实现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)
}
具体的使用就不多介绍了,就是实现接口的区别
使用场景
属性委托在kotlin中的使用是很常见的,例如延迟初始化by lazy,可观察属性Delegates.Observable,属性和Map的映射
延迟初始化
不可变的属性,在使用的时候才去初始化,属于一种懒加载
val layProp:String by lazy {
"test"
}
简单看看lazy的源码
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
lazy是一个库函数,接收一个lambda表达式,并且返回一个Lazy对象,所以,这个属性就是被委托给了Lazy类型的对象,而且延迟属性都是val修饰的,所以Lazy应该是提供了一个getValue的方法
那我们看看Lazy,发现它只是一个接口,接口里面没有我们想看到的getValue
public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
但是这个接口有一个扩展函数getValue,所以就是通过这个来实现属性委托的
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
实现延迟初始化的的原理也很简单,就是在get的时候,去执行lambda里面的内容,这里就不继续展开讲了
可观察属性Delegates.Observable
可观察属性利用了观察者模式,通过把属性委托给Delegates.Observable,能够监测到这个属性的变化,看栗子
var observablePrp by Delegates.observable(1) { property, oldValue, newValue ->
println("property is $property,old is $oldValue,new is $newValue")
}
Delegates.observable是一个函数,接收两个参数,第一个是初始值,第二个是一个lambda表达式,lambda有三个参数
- property:被修改的属性
- oldValue:原始值
- newValue:修改后的值
当这个属性值被修改后,就会回调到传入的lambda表达式,例如开发中某个属性是一个标记位,需要在标记位被修改之后立马更新到文件中,就可以使用到这个语法在lambda中去处理,非常方便
Delegates.Observable的源码是一个很标准的属性委托,函数直接返回的是一个ReadWriteProperty类型
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
属性和Map的映射
在开发中,可能会遇到这样一种场景,一个实体类跟一个map能够对应上,也就是map中存储了实体对象某些属性的值,这时候,我们可以让类的构造函数去接收一个map对象,然后将属性委托给这个map,这样就不用单独去赋值了
class TestProp(map: Map<String,Any?>){
val mapPro by map
val mapPro1 by map
}
fun main(){
val map = mapOf("mapPro" to 1,"mapPro1" to "str")
val testProp = TestProp(map)
println("map pro is ${testProp.mapPro1}")
}
查看源码可以发现,Map提供了getValue的扩展方法,当读取某个属性的值时,会在getValue中拿到这个属性的名字,然后用这个名字作为key,去调用get方法获取对应的值
public inline operator fun <V, V1 : V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 =
@Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
当然如果属性是var的,委托的map则也需要是MutableMap类型的,只有MutableMap才提供了setValue的扩展方法
以上就是关于kotlin委托的全部内容,欢迎在评论区交流