天天看點

android Kotlin 委托

前言

kotlin 作為google官方android 開發語言,大勢所趨,據傳到2018底kotlin要全面替代java在android中的地位,其實這個也不擔心畢竟kotin和java可以100%互操作。兩種語言進行編寫也是可以的。

委托模式是軟體設計模式中的一項基本技巧。在委托模式中,有兩個對象參與處理同一個請求,

接受請求的對象将請求委托給另一個對象來處理。Kotlin 直接支援委托模式,更加優雅,

簡潔。Kotlin 通過關鍵字 by 實作委托。

類委托

背景 ​

​xiaoming,xiaomingfather,xiaomingmother​

​​小明,向他爸要錢

買玩具,其實錢是他媽管,找他爸要錢的小明,最後出自他媽手中。(呵呵~~)

package KotlinAgency.Kagency

/**
 * Created by weichyang on 2017/11/2.
 * 委托方
 */
interface KXiaomingFather {
    fun payMoney()
}      

p

ackage KotlinAgency.Kagency

import KotlinAgency.XiaomingFather

/**
 * Created by weichyang on 2017/11/2
 *被委托方
 */
class KxiaomingMonther(var payMoney: Int) : XiaomingFather {

    override fun payMoney() {
        print("給小明零花錢" + payMoney + "元")
    }
}      
package KotlinAgency.Kagency

import KotlinAgency.XiaomingFather

/**
 * Created by weichyang on 2017/11/2.
 * 在 KXiaoming 聲明中,by 子句表示,将 b 儲存在 KXiaoming 的對象執行個體内部,
 * 而且編譯器将會生成繼承自 XiaomingFather 接口的所有方法, 并将調用轉發給 b。
 *
 * 也就是說,編譯器會生成 payMoney()方法,拿到Kxiaoming 對象後可以直接調用b中方法,也就是
 * KxiaomingFather中的方法
 *
 */
class KXiaoming(b: XiaomingFather) : XiaomingFather by b {

    fun buyToy(): KXiaoming {
        println("想要買買玩具")
        return this
    }
}      
package KotlinAgency.Kagency

import KotlinAgency.XiaomingMother

/**
 * Created by weichyang on 2017/11/2.
 */
object Java2KotlinAnency {

    @JvmStatic
    fun main(args: Array<String>) {
        val angency = XiaomingMother(10)
        KXiaoming(angency).buyToy().payMoney()
    }


}      

上面代碼就是java中代理寫法的Kontlin代碼。隻是寫法變了。

屬性委托

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

屬性委托需要提供getValue()setValue()方法的實作。Val隻需要提供getValue()方法的實作

屬性委托文法格式:

val/var <屬性名>: <類型> by <表達式>

var/val:屬性類型(可變/隻讀)
屬性名:屬性名稱
類型:屬性的資料類型
表達式:委托代理類      

​by​

​​ 關鍵字之後的表達式就是委托, 屬性的 get() 方法(以及set() 方法)将被委托給這個對象的 getValue() 和 setValue() 方法。

屬性委托不必實作任何接口, 但必須提供 getValue() 函數(對于 var屬性,還需要 setValue() 函數)。

定義一個被委托的類

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

輸出結果為:

Example@433c675d, 這裡委托了 p 屬性
Example@433c675d 的 p 屬性指派為 Runoob
Example@433c675d, 這裡委托了 p 屬性      

标準委托

Kotlin 的标準庫中已經内置了很多工廠方法來實作屬性的委托。

延遲屬性 Lazy

lazy() 是一個函數, 接受一個 Lambda 表達式作為參數, 傳回一個 Lazy 執行個體的函數,傳回的執行個體可以作為實作延遲屬性的委托: 第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式并記錄結果, 後續調用 get() 隻是傳回記錄的結果。

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

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

執行輸出結果:

computed!
Hello
Hello      

可觀察屬性 Observable

顧名思義,可觀察屬性,就是指屬性的變化可以通過函數進行輸出

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

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

以下響應器實作通過lambda函數來實作。

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 = "第二次指派"
}      

執行輸出結果:

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

把屬性儲存在映射中

一個常見的用例是在一個映射(map)裡存儲屬性的值。

這經常出現在像解析 JSON 或者做其他”動态”事情的應用中。

在這種情況下,你可以使用映射執行個體自身作為委托來實作委托屬性。

類似java中的Map。 注:val,var 映射方式不同,一個采用Map,一個采用mutiableMap

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 "菜鳥教程",
        "url"  to "www.runoob.com"
    ))

    // 讀取映射值
    println(site.name)
    println(site.url)
}      

Not Null

notNull 适用于那些無法在初始化階段就确定屬性值的場合。

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

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 自身。

提供委托

import kotlin.reflect.KProperty

/**
 * 就是給A和委托B之間插入一個中間件而已
 * 按照正常屬性委托流程,需要自己根據val ,var實作getValue(),或者setValue(),提供
 *委托實在屬性委托的基礎上增加一個 provideDelegate()成員函數或者擴充函數作為中間件,進行一些操作
 *來實作,感覺有點類似,面向切面程式設計的切面效果。
 */

class dge<T>(t: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 這裡委托了 ${property.name} 屬性"
    }
}

class ResourceLoader<T>(id: Int) {

    //可以擴充建立屬性實作所委托對象的邏輯
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>): dge<T?> {
        checkProperty(thisRef, prop.name)
        // 建立委托
        var t: T? = null
        return dge(t)
    }

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

fun <T> bindResource(id: Int): ResourceLoader<T> {
    return ResourceLoader<T>(id)
}

class MyUI {
    //被委托類.委托類,委托
    //将 image 和 text的 get,set屬性 委托,委托類個getValue( )
    val image by ResourceLoader<Int>(1) //兩個屬性進行了委托、實作getValue()
    val text by ResourceLoader<Int>(2)
}

fun main(args: Array<String>) {
    val myui: MyUI = MyUI()
    println(myui.image)
}