Kotlin入門-解決8個問題,就解決了 委託

在這裏插入圖片描述

前言

即,相同的事情,執行人從我 交給了 你。
不需要改動 我的部分修改就交給你完成。增加了靈活性。

本節要關注幾個問題。
① 委託是通過什麼實現的?
② 實現委託有幾個步驟,幾個組成部分
③ 委託可以改變什麼?屬性?函數重載?
④ lazy懶加載如何實現?要注意什麼?加載幾次?什麼時候加載?
⑤ Observable觀察者模式是怎麼樣的?
⑥ val&var在委託時,要注意什麼
⑦ provideDelegate是如何實現的?
⑧ 屬性映射如何實現?

包括了一下幾個部分

  • 簡介
    • 所謂委託
  • 類委託
  • 屬性委託
  • 標準委託
  • 把屬性儲存在映射中
  • Not Null
  • 局部委託屬性
  • 屬性委託要求
  • 小結

簡介

Kotlin 直接支持委託模式,更加優雅,簡潔;
Kotlin 通過關鍵字 by 實現委託


所謂委託

就是在原來調用的對象上,包了一層

通過 包裹層調用 原來的對象

是 實現繼承的一個很好的替代方法


類委託

這有3個部分

  • interface
  • interface的實現
  • 委託類
  • 外➕實際調用範例
用範例來看下 委託的3個部分
// 創建接口
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
}

當然委託也支持重載

// 通過關鍵字 by 建立委託類
class Derived(b: Base) : Base by b

改成

// 通過關鍵字 by 建立委託類
class Derived(b: Base) : Base by b {
    override fun print() { print("abc") }
    fun print2() {println("the result of print2")}
}

輸出就變成了abc


屬性委託

val/var <屬性名>: <類型> by <表達式>
不必實現任何接口,
但必須提供 getValue() 函數(對於 var屬性,還需要 setValue() 函數),定義一個被委託的類

範例

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

標準委託

延遲屬性 Lazy

範例

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

fun main(args: Array<String>) {
    println(lazyValue)   // 第一次執行,執行兩次輸出表達式
    println(lazyValue)   // 第二次執行,只輸出返回值
}

輸出

computed!
Hello
Hello
lazy是系統提供的函數

提供了同步鎖,是線程安全的;
是不是突然覺得 各種延遲初始化,變得很清晰,實現還很方便

可觀察屬性 Observable

observable 可以用於實現觀察者模式
Delegates.observable() 函數接受兩個參數

  • 第一個是初始化值
  • 第二個是屬性值變化事件的響應器(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 = "第二次賦值"
}

把屬性儲存在映射中

這部分在《Kotlin入門-屬性》中有過講解。
來複習一下。
name\url是需要初始值的,否則會編譯出錯
通過將name、url交給map自身處理,達到延遲初始化的效果
複習一下案例

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

輸出

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

Not Null

適用於那些無法在初始化階段就確定屬性值的場合

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

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

注意
不能在賦值前,被訪問

局部委託屬性

局部變量的延遲初始化

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

首先定義了 by lazy(computeFoo)
注意

  • 這裏只會進行一次初始化
  • 如果 someCondition 失敗,那麼該變量根本不會計算

屬性委託要求

必須是同類型、超類型
  • 對於 val 只讀屬性,委託必須提供一個名爲 getValue 的函數
  • 對於 var 可變屬性,委託必須額外提供一個名爲 setValue 的函數

翻譯規則

每個委託屬性的實現的背後
Kotlin 編譯器都會生成輔助屬性並委託給它

編譯後的範例學習

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

注意看$之後的東西
代表了內部外部的引用關係

提供委託(自 1.1 起)

擴展創建屬性實現 所委託對象 的邏輯

理解
  • 這提供了 委託類 的動態擴展能力
  • 便方便的實現委託類的定製化(繼承之後)
  • 也就增強了委託類的層次感

範例

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
    
class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 創建委託
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { …… }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}
thisRef

必須與 屬性所有者 類型(對於擴展屬性——指被擴展的類型)相同或者是它的超類型

property

必須是類型 KProperty<*> 或其超類型。
通過範例,可以看到委託解決了

  • 顯式傳遞屬性名
  • 提供了攔截的功能

看一段provideDelegate的生成代碼

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

// 這段代碼是當“provideDelegate”功能可用時
// 由編譯器生成的代碼:

class C {
    // 調用“provideDelegate”來創建額外的“delegate”屬性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

注意
provideDelegate 方法隻影響輔助屬性的創建;
不會影響爲 getter 或 setter 生成的代碼


小結

Kotlin的委託,提供了延遲加載lazy、繼承的第二種實現、屬性&函數的快捷修改、觀察者模式的快捷實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章