Kotlin语法糖--类和对象(下)之委托

来到这一节的最后了,委托是Java中没有的概念,我们就来学习一下吧

  • 类委托

委托模式是一个很有用的模式,它可以用来从类中抽取主要功能部分。委托模式是Kotlin原生支持的,所以它避免了我们需要去调用委托对象。委托者只需要指定实现的接口的实例即可
来看看下面的一个示例,类A2继承一个接口A,并将其所有共有的方法委托给另外一个指定的对象A1

fun main(args: Array) {
    val a2=A2(A1())
    a2.printValue()
}

interface A {
    fun printValue()
}

class A1 : A {
    override fun printValue() {
        println("A1")
    }
}

class A2(a: A) : A by a
  • 委托属性

属性对应的getter/setter可以被委托给委托表达式的getValue()和setValue()函数。属性的委托不需要实现任何接口,只要提供这个getter和setter函数(var属性需要setter)即可。委托属性的语法为:val/var <属性名>: <类型> by <表达式>,by后面的表达式就是该委托。我们来看看这个委托是什么样子的

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

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef + ${property.name} + $value")
    }
}

再来看看如何设置到属性上

class B {
    var value: String by Delegate()
}

我们在其他地方调用这个value,其实就是在调用Delegate的getValue函数。getValue函数的两个参数分别为value所在类的对象以及value的自身的描述,setValue函数就是比getValue多一个设定的值。使用的时候就像这样

B().value="HI"
println(B().value)

来看看运行结果


运行结果
  • 标准委托

Kotlin标准库为几种有用的委托提供了工厂方法
(1) 延迟属性Lazy
lazy()接收一个lambda表达式并返回一个lazy实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用getter会执行已传递给lazy()的lambda表达式并记录结果,后续只调用getter返回记录的结果

class C {
    val value: String by lazy {
        println("C")
        "1"
    }
}

操作属性

val c=C()
println(c.value)
println(c.value)

查看结果


执行结果

注意该委托只可以用在常量属性上,并且lazy属性的求值是在同步锁里计算的

(2) 可观察属性Observable
当我们给相应属性进行赋值的时候,Delegates.observable()的处理程序会被自动调用。该委托的lambda表达式中有三个参数:被赋值的属性,旧值,新值

class D {
    var name: String by Delegates.observable("no name") { kProperty, s, s1 ->
        run {
            println("$s + $s1")
        }
    }
}

调用D

val d=D()
d.name="212"
d.name="213"

运行结果


运行结果

如果你想截获一个赋值并且“否决它”,就使用vetoable

class E {
    var name: Int by Delegates.vetoable(0) {
        property, oldValue, newValue -> newValue>oldValue
    }
}

如果返回的是false的话,那么值不会发生替换

var e=E()
e.name=1
println(e.name)
e.name=0
println(e.name)

看看结果


运行结果

(3) 把属性存储在映射中
最常见的一个用例就是在一个映射里存储属性的值,这经常出现在解析JSON或者其他动态处理当中。在这种情况下我们可以使用映射实例自身作为委托来实现委托属性

class F(user: Map) {
    val name: String by user
    val age: String by user
}

使用时候就跟一般属性访问一样

val f=F(mapOf("name" to "abc", Pair("age", "30")))
println("${f.name} + ${f.age}")
  • 局部委托属性

你可以将局部变量声明为属性委托,例如你可以使一个局部常量惰性初始化

fun g(lazyFun: () -> String) {
    val lazyFunTemp by lazy(lazyFun)
    println(lazyFunTemp)
}

lazyFunTemp的初始化值在对其第一次访问时从函数lazyFun计算的返回值中得到

g {
    "g"
}
  • 属性委托要求总结

通过上述的学习,我们总结一下委托对象的要求:
(1) 对于一个只读属性(val),委托必须提供一个名为getValue的函数,该函数接收以下参数
thisRef:必须与属性所属的类相同, 或者是它的基类
property:必须是 KProperty<*>或者是它的基类
返回值:必须与属性类型相同(或者是它的子类型)
(2) 对于一个可变属性(var),委托必须提供一个名为setValue的函数,该函数接收以下参数
thisRef:同val
property:同val
value : 必须与属性类型相同(或者是它的基类型)
(3) getValue()和setValue()函数可以是委托类的成员函数,也可以是它的扩展函数。如果你需要将属性委托给一个对象,而这个对象本来没有提供这些函数,这时使用扩展函数会更便利一些。这两个函数都需要标记为operator。在自定义委托时,必须实现ReadOnlyProperty或者ReadWriteProperty接口,具体选择实现哪一个接口取决与委托对象是val属性还是var属性。以ReadWriteProperty为例,就像这样

class Delegate2 : ReadWriteProperty {
    override fun getValue(thisRef: B, property: KProperty<*>): String {
        return "$thisRef + ${property.name}"
    }

    override fun setValue(thisRef: B, property: KProperty<*>, value: String) {
        println("$thisRef + ${property.name} + $value")
    }
}
  • 提供委托

之前我们看到的是直接在委托内部实现我们的功能,但是这个显然不满足我们重用性的要求,我们就需要将执行结果的函数从外部传入。
来看下面一个例子。我现在有一个Deledate3类,这个类专门用来给H类中的属性进行委托,用户可以在by的右侧直接通过传递一个lambda表达式完成值的设定

class Delegate3(val lambdaFun: () -> T) : ReadOnlyProperty {
    override fun getValue(thisRef: H, property: KProperty<*>): T {
        return lambdaFun()
    }
}

一般情况下我们都是这样使用,就像上文介绍一个

class H {
    val value2: String by Delegate3 {
        "324"
    }
}

这样value2的值就是324,没毛病
但是我现在想发生一点点变化,在不改变委托对象的逻辑的前提下,想办法扩展其的逻辑功能,比如我这边有1个全局变量

var extra3=false
var extra4=false

我试图在函数满足一定的条件的情况下将委托对象的返回值进行改变。这里,当传进去的属性对象是value的时候,我将改变extra4的值为true

class H {
    val value: String by h(this, "value") {
        "$extra3"
    }

    val value1: String by h(this, "value1") {
        "$extra4"
    }
}

fun  h(h: H, type: String, lambdaFun: () -> T) : Delegate3 {
    if (type == "value") {
        extra4=true
    }
    return Delegate3(lambdaFun)
}

由于我不知道thisRef以及property,所以我要先把他们传到这个方法里面来。
运行一下

println(H().value)
println(H().value1)

看看结果


运行结果

value先行发生调用,所以extra4被改变了
既然ReadOnlyProperty本身的回调方法是不需要我们传thisRef以及property这2个参数,那么Kotlin中有没有其他办法在不传这2个参数的情况同样可以扩展属性所委托的对象的逻辑呢?答案是肯定的,那就是provideDelegate操作符。

class H {
    val value3: String by ResourceLoader {
        "$extra3"
    }

    val value4: String by ResourceLoader {
        "$extra4"
    }
}

class ResourceLoader(val lambdaFun: () -> T) {
    operator fun provideDelegate(thisRef: H, property: KProperty<*>) : ReadOnlyProperty {
        if (property.name == "value3") {
            extra4=true
        }
        return Delegate3(lambdaFun)
    }
}

有没有注意到我们什么参数都没有传递进去,也实现了同样的功能。看看结果


运行结果

provideDelegate不会对getter/setter生成的代码产生影响,它只辅助属性的创建

至此,Kotlin类和对象部分就讲解完毕,后面我们开始学习函数和lambda表达式

你可能感兴趣的:(Kotlin语法糖--类和对象(下)之委托)