重拾Kotlin(21)-委托

一、委托模式

委托模式是一种基本的设计模式,该模式下有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin 原生支持委托模式,可以零样板代码来实现,通过关键字 by 实现委托

interface Printer {

    fun print()
    
}

class DefaultPrinter : Printer {

    override fun print() {
         println("DefaultPrinter print")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer

fun main(args: Array<String>) {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter print
}

CustomPrinter 的 by 子句表示将会在 CustomPrinter 中存储 printer 变量,并且编译器将为 CustomPrinter 隐式生成 Printer 接口的所有抽象方法,并将这些方法的调用操作转发给 printer

此外,CustomPrinter 也可以决定自己实现部分方法或全部自己实现,但重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现

interface Printer {

    val message: String

    fun print()

    fun reprint()

}

class DefaultPrinter : Printer {

    override val message: String = "DefaultPrinter message"

    override fun print() {
        println(message)
    }

    override fun reprint() {
        println("DefaultPrinter reprint")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer {

    override val message: String = "CustomPrinter message"

    override fun reprint() {
        println("CustomPrinter reprint")
    }

}

fun main(args: Array<String>) {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter message
    printer.reprint() //CustomPrinter reprint
}

二、属性委托

Kotlin 支持通过委托属性将对一个属性的访问操作委托给另外一个对象来完成,对应的语法格式是:

val/var <属性名>: <类型> by <表达式>

属性的委托不必实现任何的接口,但需要提供一个 getValue() 方法与 setValue()(对于 var 属性),对一个属性的 get 和 set 操作会被委托给属性的委托的这两个方法

class Delegate {
    //第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    }
    //第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述,第三个参数是将要赋予的值
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    }
}

看以下的小例子,通过输出值就可以看出各个方法的调用时机

fun main(args: Array<String>) {
    val example = Example()
    println(example.strValue)
    example.strValue = "leaveC"
    println(example.strValue)

//    Example, thank you for delegating 'strValue' to me!
//    null value
//    leaveC has been assigned to 'strValue' in Example.
//    Example, thank you for delegating 'strValue' to me!
//    leaveC
}

class Delegate {

    var message: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("${thisRef?.javaClass?.name}, thank you for delegating '${property.name}' to me!")
        return message ?: "null value"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in ${thisRef?.javaClass?.name}.")
        message = value
    }
}

class Example {
    var strValue: String by Delegate()
}

三、延迟属性

lazy() 是接受一个 lambda 并返回一个 Lazy < T > 实例的函数,返回的实例可以作为实现延迟属性的委托,第一次调用 get() 会执行已传递给 lazy() 函数的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果

class Example {

    val lazyValue1: String by lazy {
        println("lazyValue1 computed!")
        "Hello"
    }

    val lazyValue2: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        println("lazyValue2 computed!")
        computeLazyValue()
    }

    private fun computeLazyValue() = "leavesC"

}

fun main(args: Array<String>) {
    val example = Example()
    println(example.lazyValue1) //lazyValue1 computed!     Hello
    println(example.lazyValue1) //Hello
    println(example.lazyValue2) //lazyValue2 computed! leavesC
}

默认情况下,对于 lazy 属性的求值是带同步锁的(synchronized),此时该值只在一个线程中计算,并且所有线程会看到相同的值

如果初始化委托的同步锁不是必需的,即如果允许多个线程同时执行,那么可以将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么可以使用 LazyThreadSafetyMode.NONE 模式, 此时不会有任何线程安全的保证以及相关的开销

四、可观察属性

Delegates.observable() 接受两个参数:初始值以及修改属性值时的回调函数。当为属性赋值后就会调用该回调函数,该回调函数包含三个参数:被赋值的属性、旧值与新值

fun main(args: Array<String>) {
    val example = Example()
    example.age = 24 //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = 27 //kProperty.name: age , oldValue: 24 , newValue: 27
}

class Example {
    var age: Int by Delegates.observable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
}

如果想要拦截一个赋值操作并判断是否进行否决,可以使用 vetoable() 函数,通过返回一个布尔值来决定是否进行拦截,该判断逻辑是在属性被赋新值生效之前进行

fun main(args: Array<String>) {
    val example = Example()
    example.age = 24  //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = -10 //kProperty.name: age , oldValue: 24 , newValue: -10
    example.age = 30  //kProperty.name: age , oldValue: 24 , newValue: 30 (oldValue 依然是 24,说明第二次的赋值操作被否决了)
}

class Example {
    var age: Int by Delegates.vetoable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
        age <= 0 //返回true 则表示拦截该赋值操作
    }
}

五、把属性储存在映射中

可以在一个 map 映射里存储属性的值,然后把属性的存取操作委托给 map 进行管理

fun main(args: Array<String>) {
    val student = Student(
        mapOf(
            "name" to "leavesC",
            "age" to 24
        )
    )
    println(student.name)
    println(student.age)
}


class Student(val map: Map<String, Any?>) {

    val name: String by map

    val age: Int by map

}

在上述示例中,属性 name 和 age 都是不可变的(val),因此 map 的类型也是 Map 而非 MutableMap(MutableMap 在赋值后可以修改),因此如果为了支持 var 属性,可以将只读的 Map 换成 MutableMap

六、局部委托属性

可以将局部变量声明为委托属性

class Printer {

    fun print() {
        println("temp.Printer print")
    }

}

fun getPrinter(): Printer {
    println("temp.Printer getPrinter")
    return Printer()
}

//局部委托
fun example(getPrinter: () -> Printer) {
    val lPrinter by lazy(getPrinter)
    val valid = true
    if (valid) {
        lPrinter.print()
    }
}

fun main() {
    example { getPrinter() }
    //temp.Printer getPrinter
    //temp.Printer print
}

委托变量只会在第一次访问时才会进行初始化,因此如果 valid 为 false 的话,getPrinter() 方法就不会被调用

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券