Kotlin 七 委託

委託模式是軟件設計模式中的一項基本技巧。在委託模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理。html

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


類委託

類的委託即一個類中定義的方法實際是調用另外一個類的對象的方法來實現的。設計模式

如下實例中派生類 Derived 繼承了接口 Base 全部方法,而且委託一個傳入的 Base 類的對象來執行這些方法。ide

// 建立接口
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。函數

 

屬性委託

屬性委託指的是一個類的某個屬性值不是在類中直接進行定義,而是將其託付給一個代理類,從而實現對該類的屬性統一管理。this

屬性委託語法格式:url

val/var <屬性名>: <類型> by <表達式>
  • var/val:屬性類型(可變/只讀)
  • 屬性名:屬性名稱
  • 類型:屬性的數據類型
  • 表達式:委託代理類

by 關鍵字以後的表達式就是委託, 屬性的 get() 方法(以及set() 方法)將被委託給這個對象的 getValue() 和 setValue() 方法。屬性委託沒必要實現任何接口, 但必須提供 getValue() 函數(對於 var屬性,還須要 setValue() 函數)。spa

定義一個被委託的類

該類須要包含 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)
}

標準委託

Kotlin 的標準庫中已經內置了不少工廠方法來實現屬性的委託。設計

延遲屬性 Lazy

lazy() 是一個函數, 接受一個 Lambda 表達式做爲參數, 返回一個 Lazy <T> 實例的函數,返回的實例能夠做爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。

 

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

fun main(args:Array<String>)
{
    println(lazyValue)// // 第一次執行,執行兩次輸出表達式
    println("-------------")
    println(lazyValue) // 第一次執行,執行兩次輸出表達式
}

  

可觀察屬性 Observable

observable 能夠用於實現觀察者模式。

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

在屬性賦值後會執行事件的響應器(handler),它有三個參數:被賦值的屬性、舊值和新值:

 

import kotlin.properties.Delegates

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

fun main(args:Array<String>)
{
    val user =User()

    user.name = "第一次賦值" //old:初始值 -> new :第一次賦值
    user.name = "第二次賦值" //old:第一次賦值 -> new :第二次賦值
}

運行結果:

old:初始值 -> new :第一次賦值
old:第一次賦值 -> new :第二次賦值

把屬性儲存在映射中

一個常見的用例是在一個映射(map)裏存儲屬性的值。 這常常出如今像解析 JSON 或者作其餘"動態"事情的應用中。 在這種狀況下,你可使用映射實例自身做爲委託來實現委託屬性。

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

fun main(args:Array<String>)
{
    val site = Site(mapOf(
        "name" to "zhaosi",
        "url" to "http://www.zhaosi.com"
    ))

    println(site.name)
    println(site.url)
}

 若是使用 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 "zhaosi",
        "url" to "www.zhaosi.com"
    )

    val sit = Site(map)

    println(sit.name)
    println(sit.url)

    println("------------")

    map.put("name","liuneng")
    map.put("url","www.liuneng.com")
    println(sit.name)
    println(sit.url)
}

  

Not Null

notNull 適用於那些沒法在初始化階段就肯定屬性值的場合。

import kotlin.properties.Delegates

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

fun main(args:Array<String>)
{
    var foo = Foo()
    foo.notNullBar = "bar"
    println(foo.notNullBar)
}

 須要注意,若是屬性在賦值前就被訪問的話則會拋出異常。

 

局部委託屬性

你能夠將局部變量聲明爲委託屬性。 例如,你可使一個局部變量惰性初始化:

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

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

memoizedFoo 變量只會在第一次訪問時計算。 若是 someCondition 失敗,那麼該變量根本不會計算。  

屬性委託要求

對於只讀屬性(也就是說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 中)檢查屬性一致性。

例如,若是要在綁定以前檢查屬性名稱,能夠這樣寫:

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

    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 的參數與 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)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

請注意,provideDelegate 方法隻影響輔助屬性的建立,並不會影響爲 getter 或 setter 生成的代碼。  

相關文章
相關標籤/搜索