kotlin 委托

委托模式是軟件設(shè)計(jì)模式中的一項(xiàng)基本技巧。在委托模式中,有兩個(gè)對(duì)象參與處理同一個(gè)請(qǐng)求,接受請(qǐng)求的對(duì)象將請(qǐng)求委托給另一個(gè)對(duì)象來處理。

Kotlin 直接支持委托模式,更加優(yōu)雅,簡(jiǎn)潔。Kotlin 通過關(guān)鍵字 by 實(shí)現(xiàn)委托。

類委托

類的委托即一個(gè)類中定義的方法實(shí)際是調(diào)用另一個(gè)類的對(duì)象的方法來實(shí)現(xiàn)的。

以下示例中派生類 Derived 繼承了接口 Base 所有方法,并且委托一個(gè)傳入的 Base 類的對(duì)象來執(zhí)行這些方法。

// 創(chuàng)建接口
interface Base {   
    fun print()
}
// 實(shí)現(xiàn)此接口的被委托的類
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}
// 通過關(guān)鍵字 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 的對(duì)象示例內(nèi)部,而且編譯器將會(huì)生成繼承自 Base 接口的所有方法, 并將調(diào)用轉(zhuǎn)發(fā)給 b。

屬性委托

屬性委托指的是一個(gè)類的某個(gè)屬性值不是在類中直接進(jìn)行定義,而是將其托付給一個(gè)代理類,從而實(shí)現(xiàn)對(duì)該類的屬性統(tǒng)一管理。

屬性委托語法格式:

val/var <屬性名>: <類型> by <表達(dá)式>
  • var/val:屬性類型(可變/只讀)

  • 屬性名:屬性名稱

  • 類型:屬性的數(shù)據(jù)類型

  • 表達(dá)式:委托代理類

by 關(guān)鍵字之后的表達(dá)式就是委托, 屬性的 get() 方法(以及set() 方法)將被委托給這個(gè)對(duì)象的 getValue() 和 setValue() 方法。屬性委托不必實(shí)現(xiàn)任何接口, 但必須提供 getValue() 函數(shù)(對(duì)于 var屬性,還需要 setValue() 函數(shù))。

定義一個(gè)被委托的類

該類需要包含 getValue() 方法和 setValue() 方法,且參數(shù) thisRef 為進(jìn)行委托的類的對(duì)象,prop 為進(jìn)行委托的屬性的對(duì)象。

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)     // 訪問該屬性,調(diào)用 getValue() 函數(shù)
    e.p = "Nhooo"   // 調(diào)用 setValue() 函數(shù)
    println(e.p)
}

輸出結(jié)果為:

Example@433c675d, 這里委托了 p 屬性
Example@433c675d 的 p 屬性賦值為 Nhooo
Example@433c675d, 這里委托了 p 屬性

標(biāo)準(zhǔn)委托

Kotlin 的標(biāo)準(zhǔn)庫中已經(jīng)內(nèi)置了很多工廠方法來實(shí)現(xiàn)屬性的委托。

延遲屬性 Lazy

lazy() 是一個(gè)函數(shù), 接受一個(gè) Lambda 表達(dá)式作為參數(shù), 返回一個(gè) Lazy <T> 示例的函數(shù),返回的示例可以作為實(shí)現(xiàn)延遲屬性的委托: 第一次調(diào)用 get() 會(huì)執(zhí)行已傳遞給 lazy() 的 lamda 表達(dá)式并記錄結(jié)果, 后續(xù)調(diào)用 get() 只是返回記錄的結(jié)果。

val lazyValue: String by lazy {
    println("computed!")     // 第一次調(diào)用輸出,第二次調(diào)用不執(zhí)行
    "Hello"
}
fun main(args: Array<String>) {
    println(lazyValue)   // 第一次執(zhí)行,執(zhí)行兩次輸出表達(dá)式
    println(lazyValue)   // 第二次執(zhí)行,只輸出返回值
}

執(zhí)行輸出結(jié)果:

computed!
Hello
Hello

可觀察屬性 Observable

observable 可以用于實(shí)現(xiàn)觀察者模式。

Delegates.observable() 函數(shù)接受兩個(gè)參數(shù): 第一個(gè)是初始化值, 第二個(gè)是屬性值變化事件的響應(yīng)器(handler)。

在屬性賦值后會(huì)執(zhí)行事件的響應(yīng)器(handler),它有三個(gè)參數(shù):被賦值的屬性、舊值和新值:

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 = "第二次賦值"
}

執(zhí)行輸出結(jié)果:

舊值:初始值 -> 新值:第一次賦值
舊值:第一次賦值 -> 新值:第二次賦值

把屬性儲(chǔ)存在映射中

一個(gè)常見的用例是在一個(gè)映射(map)里存儲(chǔ)屬性的值。 這經(jīng)常出現(xiàn)在像解析 JSON 或者做其他"動(dòng)態(tài)"事情的應(yīng)用中。 在這種情況下,你可以使用映射示例自身作為委托來實(shí)現(xiàn)委托屬性。

class Site(val map: Map<String, Any?>) {
    val name: String by map
    val url: String  by map
}
fun main(args: Array<String>) {
    // 構(gòu)造函數(shù)接受一個(gè)映射參數(shù)
    val site = Site(mapOf(
        "name" to "菜鳥教程",
        "url"  to "www.soo66.com"
    ))
    
    // 讀取映射值
    println(site.name)
    println(site.url)
}

執(zhí)行輸出結(jié)果:

菜鳥教程
www.soo66.com

如果使用  var 屬性,需要把 Map 換成 MutableMap:

class Site(val map: MutableMap<String, Any?>) {
    val name: String by map
    val url: String by map
}
fun main(args: Array<String>) {
    var map:MutableMap<String, Any?> = mutableMapOf(
            "name" to "菜鳥教程",
            "url" to "www.soo66.com"
    )
    val site = Site(map)
    println(site.name)
    println(site.url)
    println("--------------")
    map.put("name", "Google")
    map.put("url", "www.google.com")
    println(site.name)
    println(site.url)
}

執(zhí)行輸出結(jié)果:

菜鳥教程
www.soo66.com
--------------
Google
www.google.com

Not Null

notNull 適用于那些無法在初始化階段就確定屬性值的場(chǎng)合。

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}
foo.notNullBar = "bar"
println(foo.notNullBar)

需要注意,如果屬性在賦值前就被訪問的話則會(huì)拋出異常。

局部委托屬性

你可以將局部變量聲明為委托屬性。 例如,你可以使一個(gè)局部變量惰性初始化:

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

memoizedFoo 變量只會(huì)在第一次訪問時(shí)計(jì)算。 如果 someCondition 失敗,那么該變量根本不會(huì)計(jì)算。

屬性委托要求

對(duì)于只讀屬性(也就是說val屬性), 它的委托必須提供一個(gè)名為getValue()的函數(shù)。該函數(shù)接受以下參數(shù):

  • thisRef —— 必須與屬性所有者類型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型

  • property —— 必須是類型 KProperty 或其超類型

這個(gè)函數(shù)必須返回與屬性相同的類型(或其子類型)。

對(duì)于一個(gè)值可變(mutable)屬性(也就是說,var 屬性),除 getValue()函數(shù)之外,它的委托還必須 另外再提供一個(gè)名為setValue()的函數(shù), 這個(gè)函數(shù)接受以下參數(shù):

  • thisRef —— 必須與屬性所有者 類型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型

  • property —— 必須是類型 KProperty 或其超類型

  • new value —— 必須和屬性同類型或者是它的超類型。

編譯規(guī)則

在每個(gè)委托屬性的實(shí)現(xiàn)的背后,Kotlin 編譯器都會(huì)生成輔助屬性并委托給它。 例如,對(duì)于屬性 prop,生成隱藏屬性 prop$delegate,而訪問器的代碼只是簡(jiǎn)單地委托給這個(gè)附加屬性:

class C {
    var prop: Type by MyDelegate()
}
// 這段是由編譯器生成的相應(yīng)代碼:
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 編譯器在參數(shù)中提供了關(guān)于 prop 的所有必要信息:第一個(gè)參數(shù) this 引用到外部類 C 的示例而 this::prop 是 KProperty 類型的反射對(duì)象,該對(duì)象描述 prop 自身。

提供委托

通過定義 provideDelegate 操作符,可以擴(kuò)展創(chuàng)建屬性實(shí)現(xiàn)所委托對(duì)象的邏輯。 如果 by 右側(cè)所使用的對(duì)象將 provideDelegate 定義為成員或擴(kuò)展函數(shù),那么會(huì)調(diào)用該函數(shù)來 創(chuàng)建屬性委托示例。

provideDelegate 的一個(gè)可能的使用場(chǎng)景是在創(chuàng)建屬性時(shí)(而不僅在其 getter 或 setter 中)檢查屬性一致性。

例如,如果要在綁定之前檢查屬性名稱,可以這樣寫:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 創(chuàng)建委托
    }
    private fun checkProperty(thisRef: MyUI, name: String) { …… }
}
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }
class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 的參數(shù)與 getValue 相同:

  • thisRef —— 必須與 屬性所有者 類型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型

  • property —— 必須是類型 KProperty 或其超類型。

在創(chuàng)建 MyUI 示例期間,為每個(gè)屬性調(diào)用 provideDelegate 方法,并立即執(zhí)行必要的驗(yàn)證。

如果沒有這種攔截屬性與其委托之間的綁定的能力,為了實(shí)現(xiàn)相同的功能, 你必須顯式傳遞屬性名,這不是很方便:

// 檢查屬性名稱而不使用“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)
   // 創(chuàng)建委托
}

在生成的代碼中,會(huì)調(diào)用 provideDelegate 方法來初始化輔助的 prop$delegate 屬性。 比較對(duì)于屬性聲明 val prop: Type by MyDelegate() 生成的代碼與 上面(當(dāng) provideDelegate 方法不存在時(shí))生成的代碼:

class C {
    var prop: Type by MyDelegate()
}
// 這段代碼是當(dāng)“provideDelegate”功能可用時(shí)
// 由編譯器生成的代碼:
class C {
    // 調(diào)用“provideDelegate”來創(chuàng)建額外的“delegate”屬性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

請(qǐng)注意,provideDelegate 方法只影響輔助屬性的創(chuàng)建,并不會(huì)影響為 getter 或 setter 生成的代碼。

丰满人妻一级特黄a大片,午夜无码免费福利一级,欧美亚洲精品在线,国产婷婷成人久久Av免费高清