美文网首页
Kotlin学习之类与对象3

Kotlin学习之类与对象3

作者: James999 | 来源:发表于2020-05-08 11:58 被阅读0次

    委托

    由委托实现

    委托模式(https://zh.wikipedia.org/wiki/委托模式)已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base :

    derived [dɪ'raɪvd]
    adj. 导出的;衍生的,派生的
    v. 从……衍生出,源于;(从……中)得到,提取;导出(derive 的过去式和过去分词)

    interface Base {
        fun print()
    }
    class BaseImpl(val x: Int) : Base {
        override fun print() { print(x) }
    }
    class Derived(b: Base) : Base by b
    fun main() {
        val b = BaseImpl(10)
        Derived(b).print()
    }
    

    Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。

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

    覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。如果将 override fun printMessage() { print("abc") } 添加到 Derived ,那么当调用 printMessage 时程序会输出“abc”而不是“10”:

    interface Base {
        fun printMessage()
        fun printMessageLine()
    }
    class BaseImpl(val x: Int) : Base {
        override fun printMessage() { print(x) }
        override fun printMessageLine() { println(x) }
    }
    class Derived(b: Base) : Base by b {
        override fun printMessage() { print("abc") }
    }
    fun main() {
        val b = BaseImpl(10)
        Derived(b).printMessage()
        Derived(b).printMessageLine()
    }
    

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

    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)
    }
    

    委托属性

    有一些常⻅的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:

    • 延迟属性(lazy properties): 其值只在首次访问时计算;
    • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
    • 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
      为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性:
    class Example {
        var p: String by Delegate()
    }
    

    语法是: val/var <属性名>: <类型> by <表达式> 。在 by 后面的表达式是该 委托, 因为属性对应的 get() (与 set() )会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue() ⸺对于 var 属性)。 例如:
    委托对象的要求规范可以在下文找到。
    请注意,自 Kotlin 1.1 起你可以在函数或代码块中声明一个委托属性,因此它不一定是类的成员。 你可以在下文找到其示例。

    标准委托

    Kotlin 标准库为几种有用的委托提供了工厂方法。

    延迟属性 Lazy

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

    val lazyValue: String by lazy {
        println("lazy computed!")
        "Hello"
    }
    
    fun main() {
         println(lazyValue)
         println(lazyValue)
     }
    

    默认情况下,对于 lazy 属性的求值是同步锁的(synchronized) :该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将
    LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。

    可观察属性 Observable

    Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:

    import kotlin.properties.Delegates
    class User {
        var name: String by Delegates.observable("<no name>") {
                prop, old, new ->
            println("$old -> $new")
        }
    }
    fun main() {
        val user = User()
        user.name = "first"
        user.name = "second"
    }
    

    把属性储存在映射中

    一个常⻅的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
    如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable() 。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。

    class User(val map: Map<String, Any?>) {
       val name: String by map
       val age: Int  by map
    }
    

    在这个例子中,构造函数接受一个映射参数:

    val user = User(mapOf(
    "name" to "John Doe",
    "age" to 25
    ))
    

    委托属性会从这个映射中取值(通过字符串键⸺属性的名称):

    println(user.name)    // Prints "John Doe"
    println(user.age)       // Prints 25
    

    这也适用于 var 属性,如果把只读的 Map 换成 MutableMap 的话:

    class MutableUser(val map: MutableMap<String, Any?>) {
       var name: String by map
       var age: Int by map
    }
    

    局部委托属性(自 1.1 起)

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

    fun example(computeFoo: () -> Foo) {
        val memoizedFoo by lazy(computeFoo)
        if (someCondition && memoizedFoo.isValid()) {
            memoizedFoo.doSomething()
        }
    }
    

    memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

    属性委托要求

    这里我们总结了委托对象的要求。

    1. 对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数:
      • thisRef ⸺ 必须与 属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型;
      • property ⸺ 必须是类型 KProperty<*> 或其超类型。
        这个函数必须返回与属性相同的类型(或其子类型)。
    2. 对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数:
      • thisRef ⸺ 同 getValue() ;
      • property ⸺ 同 getValue() ;
      • new value ⸺ 必须与属性同类型或者是它的子类型。
        getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。
        委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:
    interface ReadOnlyProperty<in R, out T> {
        operator fun getValue(thisRef: R, property: KProperty<*>): T
    }
    interface ReadWriteProperty<in R, T> {
        operator fun getValue(thisRef: R, property: KProperty<*>): T
        operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    }
    

    翻译规则

    在每个委托属性的实现的背后,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 自身。
    请注意,直接在代码中引用绑定的可调用引用的语法 this::prop 自 Kotlin 1.1 起才可用。

    提供委托(自 1.1 起)

    通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。
    provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检测属性一致性。例如,如果要在绑定之前检测属性名称,可以这样写:
    provideDelegate 的参数与 getValue 相同:

    • thisRef ⸺ 必须与属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型;
    • property ⸺ 必须是类型 KProperty<*> 或其超类型。
      在创建 MyUI 实例期间,为每个属性调用 provideDelegate 方法,并立即执行必要的验证。
      如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名,这不是很方便:
    // 检测属性名称而不使用“provideDelegate”功能
    class MyUI {
        val image by bindResource(ResourceID.image_id, "image")
        val text by bindResource(ResourceID.text_id, "text")
    }
    fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(this, propertyName)
    // 创建委托
    }
    

    在生成的代码中,会调用 provideDelegate 方法来初始化辅助的 prop$delegate 属性。 比较对于属性声明 val prop: Type by MyDelegate() 生成的代码与上面(当 provideDelegate 方法不存在时)生成的代码:

    class C {
        var prop: Type by MyDelegate()
    }
    // 这段代码是当“provideDelegate”功能可用时
    // 由编译器生成的代码:
    class C {
        // 调用“provideDelegate”来创建额外的“delegate”属性
        private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
        var prop: Type
            get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
    }
    

    请注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。

    相关文章

      网友评论

          本文标题:Kotlin学习之类与对象3

          本文链接:https://www.haomeiwen.com/subject/gnuaghtx.html