Kotlin学习笔记15——委托

前言

上一篇,我们学习了Kotlin中的object关键字,今天继续来学习Kotlin中的委托。

kotlin 委托

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。

// 创建接口
interface Base {
    
       
    fun print()
}

// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
    
    
    override fun print() {
    
     print(x) }
}

// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    
    
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。

覆盖由委托实现的接口成员

编译器会使用 override 覆盖的实现而不是委托对象中的。

interface Base {
    
    
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    
    
    override fun printMessage() {
    
     println("BaseImpl printMessage $x") }
    override fun printMessageLine() {
    
     println("BaseImpl printMessageLine $x") }
}

class Derived(b: Base) : Base by b {
    
    
    override fun printMessage() {
    
     println("override printMessage abc") }
}

fun main() {
    
    
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}

打印结果:

override printMessage abc
BaseImpl printMessageLine 10

但请注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现:

interface Base {
    
    
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    
    
    override val message = "BaseImpl: x = $x"
    override fun print() {
    
     println(message) }
}

class Derived(b: Base) : Base by b {
    
    
    // 在 b 的 `print` 实现中不会访问到这个属性
    override val message = "Message of Derived"
}

fun main() {
    
    
    val b = BaseImpl(10)
    val derived = Derived(b)
    //委托对象的成员函数中调用 ,委托对象的成员只能访问其自身对接口成员实现
    derived.print()
    println(derived.message)
}

打印结果:

BaseImpl: x = 10
Message of Derived

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

属性委托语法格式:

val/var <属性名>: <类型> by <表达式>
  • var/val:属性类型(可变/只读)
  • 属性名:属性名称
  • 类型:属性的数据类型
  • 表达式:委托代理类

by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。

定义一个被委托的类,该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
    
    
    var p: String by Delegate()
}

// 委托的类
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")
    }
}
fun main(args: Array<String>) {
    
    
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}

输出结果:

Example@433c675d, 这里委托了 p 属性
Example@433c675d 的 p 属性赋值为 Runoob
Example@433c675d, 这里委托了 p 属性

标准委托

Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    
    
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"
}

fun main(args: Array<String>) {
    
    
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值
}

输出结果:

computed!
Hello
Hello

可观察属性 Observable

observable 可以用于实现观察者模式。Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates

class User {
    
    
    var name: String by Delegates.observable("初始值") {
    
    
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
}

fun main(args: Array<String>) {
    
    
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"
}

输出结果:

旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值

把属性储存在映射中

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class Site(val map: Map<String, Any?>) {
    
    
    val fristName: String by map
    val lastName: String  by map
}

fun main(args: Array<String>) {
    
    
    // 构造函数接受一个映射参数
    val p = Site(mapOf(
        "fristName" to "Jim",
        "lastName"  to "Green"
    ))
    
    // 读取映射值
    println(p.fristName)
    println(p.lastName)
}

输出结果:

Jim
Green

Not Null

notNull 适用于那些无法在初始化阶段就确定属性值的场合。需要注意,如果属性在赋值前就被访问的话则会抛出异常。

class Foo {
    
    
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)

属性委托要求

对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:

  • thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型

这个函数必须返回与属性相同的类型(或其子类型)。对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:

  • property —— 必须是类型 KProperty<*> 或其超类型
  • new value —— 必须和属性同类型或者是它的超类型。

翻译规则

在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C {
    
    
    var prop: Type by MyDelegate()
}

// 这段是由编译器生成的相应代码:
class C {
    
    
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

提供委托

通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例。

provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。provideDelegate 的参数与 getValue 相同:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型。

示例:

class MyProvider {
    
    
    operator fun provideDelegate(thisRef: MyTestClass, prop: KProperty<*>): ReadOnlyProperty<MyTestClass, String> {
    
    
        println("do something") // 这行代码是在getValue方法之外调用的
        return object: ReadOnlyProperty<MyTestClass, String> {
    
                // 这里才是调用getValue方法的地方
            override fun getValue(thisRef: MyTestClass, property: KProperty<*>): String {
    
    
                return "aaa" 
            }   
        } 
    }
}
class MyTestClass {
    
    
    val myProvider = MyProvider()
    val myField1: String by myProvider
}
// 测试
fun main(args: Array<String>) {
    
        
    val myTestClass = MyTestClass()
    println(myTestClass.myField1)
}

输出结果:

do something
aaa

能看出 println(“do something”) 是在 getValue 方法之外运行,假如 println(“do something”)这行代码就代表了博主所说的“检查属性一致性”的代码,那么这样做就实现了博主所说的“扩展所委托对象的逻辑,在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性”;

还有一点大家注意一下,provideDelegate 这个方法的名称是不能随便起的,只能用这个名字,如果换了其它名字就会在 operator 关键字处报 ‘operator’ modifier is inapplicable on this function: illegal function name; 最后说明一下这段代码中用到的范型,ReadOnlyProperty<R, T> 要求传入两个范型,第一个范型 R 代表我们要 get 的属性所在类,第二个范型T代表我们要 get 的属性的类型。

尾巴

今天的学习笔记就先到这里了,下一篇我们将继续学习Kotlin中的集合
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

猜你喜欢

转载自blog.csdn.net/abs625/article/details/106923819