溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

發(fā)布時(shí)間:2023-03-25 17:50:18 來(lái)源:億速云 閱讀:156 作者:iii 欄目:開(kāi)發(fā)技術(shù)

今天小編給大家分享一下Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

前言

在設(shè)計(jì)模式中,委托模式(Delegate Pattern)與代理模式都是我們常用的設(shè)計(jì)模式(Proxy Pattern),兩者非常的相似,又有細(xì)小的區(qū)分。

委托模式中,委托對(duì)象和被委托對(duì)象都是同一類(lèi)型的對(duì)象,委托對(duì)象將任務(wù)委托給被委托對(duì)象來(lái)完成。委托模式可以用于實(shí)現(xiàn)事件監(jiān)聽(tīng)器、回調(diào)函數(shù)等功能。

代理模式中,代理對(duì)象與被代理對(duì)象是兩種不同的對(duì)象,代理對(duì)象代表被代理對(duì)象的功能,代理對(duì)象可以控制客戶(hù)對(duì)被代理對(duì)象的訪(fǎng)問(wèn)。代理模式可以用于實(shí)現(xiàn)遠(yuǎn)程代理、虛擬代理、安全代理等功能。

以類(lèi)的委托與代理來(lái)舉例,委托對(duì)象和被委托對(duì)象都實(shí)現(xiàn)了同一個(gè)接口或繼承了同一個(gè)類(lèi),委托對(duì)象將任務(wù)委托給被委托對(duì)象來(lái)完成。代理模式中,代理對(duì)象與被代理對(duì)象實(shí)現(xiàn)了同一個(gè)接口或繼承了同一個(gè)類(lèi),代理對(duì)象代表被代理對(duì)象,客戶(hù)端通過(guò)代理對(duì)象來(lái)訪(fǎng)問(wèn)被代理對(duì)象。

兩者的區(qū)別:

他們雖然都有同一個(gè)接口,主要區(qū)別在于委托模式中委托對(duì)象和被委托對(duì)象是同一類(lèi)型的對(duì)象,而代理模式中代理對(duì)象與被代理對(duì)象是兩種不同的對(duì)象??偟膩?lái)說(shuō),委托模式是為了將方法的實(shí)現(xiàn)交給其他類(lèi)去完成,而代理模式則是為了控制對(duì)象的訪(fǎng)問(wèn),并在訪(fǎng)問(wèn)前后進(jìn)行額外的操作。

而我們常用的委托模式怎么使用?在 Java 語(yǔ)言中需要我們手動(dòng)的實(shí)現(xiàn),而在 Kotlin 語(yǔ)言中直接通過(guò)關(guān)鍵字 by 就可以實(shí)現(xiàn)委托,其實(shí)現(xiàn)更加優(yōu)雅、簡(jiǎn)潔了。

我們?cè)陂_(kāi)發(fā)一個(gè) Android 應(yīng)用中,常用到的委托分為:

  • 接口/類(lèi)的委托

  • 屬性的委托

  • 結(jié)合lazy的延遲委托

  • 觀察者的委托

  • Map數(shù)據(jù)的委托

一、接口/類(lèi)委托

我們可以選擇使用接口來(lái)實(shí)現(xiàn)類(lèi)似的效果,也可以直接傳參,當(dāng)然接口的方式更加的靈活,比如我們這里就以接口比如我定義一個(gè)攻擊與防御的行為接口:

interface IUserAction {

    fun attack()

    fun defense()
}

定義了用戶(hù)的行為,有攻擊和防御兩種操作!接下來(lái)我們就定義一個(gè)默認(rèn)的實(shí)現(xiàn)類(lèi):

class UserActionImpl : IUserAction {

    override fun attack() {
        YYLogUtils.w("默認(rèn)操作-開(kāi)始執(zhí)行攻擊")
    }

    override fun defense() {
        YYLogUtils.w("默認(rèn)操作-開(kāi)始執(zhí)行防御")
    }
}

都是很簡(jiǎn)單的代碼,我們定義一些默認(rèn)的操作,如果任意類(lèi)想擁有攻擊和防御的能力就直接實(shí)現(xiàn)這個(gè)接口,如果想自定義攻擊和防御則重寫(xiě)對(duì)應(yīng)的方法即可。

如果使用 Java 的方式實(shí)現(xiàn)委托,大致代碼如下:

class UserDelegate1(private val action: IUserAction) : IUserAction {
    override fun attack() {
        YYLogUtils.w("UserDelegate1-需要自己實(shí)現(xiàn)攻擊")
    }

    override fun defense() {
        YYLogUtils.w("UserDelegate1-需要自己實(shí)現(xiàn)防御")
    }
}

如果使用 Kotlin 的方式實(shí)現(xiàn)則是:

class UserDelegate2(private val action: IUserAction) : IUserAction by action

如果 Kotlin 的實(shí)現(xiàn)不想默認(rèn)的實(shí)現(xiàn)也可以重寫(xiě)部分的操作:

class UserDelegate3(private val action: IUserAction) : IUserAction by action {

    override fun attack() {
        YYLogUtils.w("UserDelegate3 - 只重寫(xiě)了攻擊")
    }
}

那么使用起來(lái)就是這樣的:

    val actionImpl = UserActionImpl()

    UserDelegate1(actionImpl).run {
        attack()
        defense()
    }

    UserDelegate2(actionImpl).run {
        attack()
        defense()
    }

    UserDelegate3(actionImpl).run {
        attack()
        defense()
    }

打印日志如下:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

其實(shí)在 Android 源碼中也有不少委托的使用,例如生命周期的 Lifecycle 委托:

Lifecycle 通過(guò)委托機(jī)制實(shí)現(xiàn)其功能。具體來(lái)說(shuō),組件可以將自己的生命周期狀態(tài)委托給 LifecycleOwner 對(duì)象,LifecycleOwner 對(duì)象則負(fù)責(zé)管理這些組件的生命周期。

例如,在一個(gè) Activity 中,我們可以通過(guò)將 Activity 對(duì)象作為 LifecycleOwner 對(duì)象,并將該對(duì)象傳遞給需要注冊(cè)生命周期的組件,從而實(shí)現(xiàn)組件的生命周期管理。 頁(yè)面可以使用 getLifecycle() 方法來(lái)獲取它所依賴(lài)的 LifecycleOwner 對(duì)象的 Lifecycle 實(shí)例,并在需要時(shí)將自身的生命周期狀態(tài)委托給該 Lifecycle 實(shí)例。

通過(guò)這種委托機(jī)制,Lifecycle 實(shí)現(xiàn)了一種方便的方式來(lái)管理組件的生命周期,避免了手動(dòng)管理生命周期帶來(lái)的麻煩和錯(cuò)誤。

class AnimUtil private constructor() : DefaultLifecycleObserver {
  
    ...

    private fun addLoopLifecycleObserver() {
        mOwner?.lifecycle?.addObserver(this)
    }

    // 退出頁(yè)面的時(shí)候釋放資源
    override fun onDestroy(owner: LifecycleOwner) {
        mAnim?.cancel()
        destory()
    }

}

除此之外委托還特別適用于一些可配置的功能,比如 Resutl-Api 的封裝,如果當(dāng)前頁(yè)面需要開(kāi)啟 startActivityForResult 的功能,就實(shí)現(xiàn)這個(gè)接口,不需要這個(gè)功能就不實(shí)現(xiàn)接口,達(dá)到可配置的效果。

/**
 * 定義是否需要SAFLauncher
 */
interface ISAFLauncher {

    fun <T : ActivityResultCaller> T.initLauncher()

    fun getLauncher(): GetSAFLauncher?

}

由于代碼是固定的實(shí)現(xiàn),目標(biāo)Activity也不需要重新實(shí)現(xiàn),我們只需要實(shí)現(xiàn)默認(rèn)的實(shí)現(xiàn)即可:

class SAFLauncher : ISAFLauncher {

    private var safLauncher: GetSAFLauncher? = null

    override fun <T : ActivityResultCaller> T.initLauncher() {
        safLauncher = GetSAFLauncher(this)
    }

    override fun getLauncher(): GetSAFLauncher? = safLauncher

}

使用起來(lái)我們直接用默認(rèn)的實(shí)現(xiàn)即可:

class DemoActivity : BaseActivity, ISAFLauncher by SAFLauncher() {

    override fun init() {
        initLauncher()  // 實(shí)現(xiàn)了接口還需要初始化Launcher
    }

    fun gotoOtherPage() {
        //使用 Result Launcher 的方式啟動(dòng),并獲取到返回值
        getLauncher()?.launch<DemoCircleActivity> { result ->
            val result = result.data?.getStringExtra("text")
            toast("收到返回的數(shù)據(jù):$result")
        }

    }

}

這樣是不是就非常簡(jiǎn)單了呢?具體如何使用封裝 Result Launcher 可以看看我去年的文章 【傳送門(mén)】

二、屬性委托

除了類(lèi)與接口對(duì)象的委托,我們還常用于屬性的委托。

我知道了!這么弄就行了。

private val textStr by "123"

哎?怎么報(bào)錯(cuò)了?其實(shí)不是這么用的。

屬性委托和類(lèi)委托一樣,屬性的委托其實(shí)是對(duì)屬性的 set/get 方法的委托。

需要我們把 set/get 方法委托給 setValue/getValue 方法,因此被委托類(lèi)(真實(shí)類(lèi))需要提供 setValue/getValue 方法,val屬性只需要提供 getValue 方法。

我們修改代碼如下:

    private val textStr by TextDelegate()

    class TextDelegate {

        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return "我是賦值給與的文本"
        }

    }

打印的結(jié)果:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

而我們定義一個(gè)可讀寫(xiě)的屬性則可以

  private var textStr by TextDelegate()

    class TextDelegate {

        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return "我是賦值給與的文本"
        }

        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
            YYLogUtils.w("設(shè)置的值為:$value")
        }

    }

    YYLogUtils.w("textStr:$textStr")
    textStr = "abc123"

打印則如下:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

為了怕大家寫(xiě)錯(cuò),我們其實(shí)可以用接口來(lái)限制,只讀的和讀寫(xiě)的屬性,我們分別可以用 ReadOnlyProperty 與 ReadWriteProperty 來(lái)限制:

class TextDelegate : ReadOnlyProperty<Any, String> {
        override fun getValue(thisRef: Any, property: KProperty<*>): String {
            return "我是賦值給與的文本"
        }
    }

    class TextDelegate : ReadWriteProperty<Any, String> {
        override fun getValue(thisRef: Any, property: KProperty<*>): String {
            return "我是賦值給與的文本"
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
            YYLogUtils.w("設(shè)置的值為:$value")
        }
    }

那么實(shí)現(xiàn)的方式和上面自己實(shí)現(xiàn)的效果是一樣的。如果要使用屬性委托可以選用這種接口限制的方式實(shí)現(xiàn)。

我們的屬性除了委托給類(lèi)去實(shí)現(xiàn),同時(shí)也能委托給其他屬性(Kotlin 1.4+)來(lái)實(shí)現(xiàn),例如:

    private var textStr by TextDelegate2()
    private var textStr2 by this::textStr

其實(shí)是內(nèi)部委托了對(duì)象的 get 和 set 函數(shù)。相對(duì)委托對(duì)象而言性能更好一些。而委托對(duì)象去實(shí)現(xiàn),不僅增加了一個(gè)委托類(lèi),而且還還在初始化時(shí)就創(chuàng)建了委托類(lèi)的實(shí)例對(duì)象,算起來(lái)其實(shí)性能并不好。

所以屬性的委托不要濫用,如果要用,可以選擇委托現(xiàn)成的其他屬性來(lái)完成,或者使用延遲委托Lazy實(shí)現(xiàn),或者使用更簡(jiǎn)單的方式實(shí)現(xiàn):

    private val industryName: String
        get() {
            return "abc123"
        }

對(duì)于只讀的屬性,這種方式也是我們常見(jiàn)的使用方式。

三、延遲委托

如果說(shuō)使用類(lèi)來(lái)實(shí)現(xiàn)委托不那么好的話(huà),其實(shí)我們可以使用延遲委托。延遲關(guān)鍵字 lazy 接收一個(gè) lambda 表達(dá)式,最后一行代表返回值給被推脫的屬性。

默認(rèn)的 Lazy 實(shí)現(xiàn):

    val name: String by lazy {
        YYLogUtils.w("第一次調(diào)用初始化")
        "abc123"
    }

    YYLogUtils.w(name)
    YYLogUtils.w(name)
    YYLogUtils.w(name)

只有在第一次使用此屬性的時(shí)候才會(huì)初始化,一旦初始化之后就可以直接獲取到值。

日志打?。?/p>

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

它的內(nèi)部其實(shí)也是使用的是類(lèi)的委托實(shí)現(xiàn)。

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

最終的實(shí)現(xiàn)是由 SynchronizedLazyImpl 類(lèi)生成并實(shí)現(xiàn)的:

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

我們可以直接看 value 的 get 方法,如果_v1 !== UNINITIALIZED_VALUE 則表明已經(jīng)初始化過(guò)了,就直接返回 value ,否則表明沒(méi)有初始化過(guò),調(diào)用initializer方法,也就是 lazy 的 lambda 表達(dá)式返回屬性的賦值。

跟我們自己實(shí)現(xiàn)類(lèi)的委托類(lèi)似,也是實(shí)現(xiàn)了getValue方法。只是多了判斷是否初始化的一些相關(guān)邏輯。

lazy的參數(shù)分為三種類(lèi)型:

  • SYNCHRONIZED:添加同步鎖,使lazy延遲初始化線(xiàn)程安全

  • PUBLICATION:初始化的lambda表達(dá)式,可以在同一時(shí)間多次調(diào)用,但是只有第一次的返回值作為初始化值

  • NONE:沒(méi)有同步鎖,非線(xiàn)程安全

默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步鎖的(synchronized),是可以保證線(xiàn)程安全的,但是如果不需要線(xiàn)程安全和減少性能花銷(xiāo)可以可以使用 lazy(LazyThreadSafetyMode.NONE){} 即可。

四、觀察者委托

除了對(duì)屬性的值進(jìn)行委托,我們甚至還能對(duì)觀察到這個(gè)變化過(guò)程:

使用 observable 委托監(jiān)聽(tīng)值的變化:

    var values: String by Delegates.observable("默認(rèn)值") { property, oldValue, newValue ->

        YYLogUtils.w("打印值: $oldValue -> $newValue ")
    }

    values = "第一次修改"
    values = "第二次修改"
    values = "第三次修改"

打印:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

我們還能使用 vetoable 委托,和 observable 一樣可以觀察屬性的變化,不同的是 vetoable 可以決定是否使用新值。

    var age: Int by Delegates.vetoable(18) { property, oldValue, newValue ->
        newValue > oldValue
    }

    YYLogUtils.w("age:$age")
    age = 14
    YYLogUtils.w("age:$age")
    age = 20
    YYLogUtils.w("age:$age")
    age = 22
    YYLogUtils.w("age:$age")
    age = 20
    YYLogUtils.w("age:$age")

我們需要返回 booble 值覺(jué)得是否使用新值,比如上述的例子就是當(dāng)新值大于老值的時(shí)候才賦值。那么打印的日志就是如下:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

雖然這種方式我們并不常用,一般我們都是使用類(lèi)似 Flow 之類(lèi)的工具在源頭就處理了邏輯,使用這種方式我們就可以在屬性的賦值過(guò)程中進(jìn)行攔截了。在一些特定的場(chǎng)景下還是有用的。

五、Map委托

我們的屬性不止可以使用類(lèi)的委托,延遲的委托,觀察的委托,還能委托Map來(lái)進(jìn)行賦值。

當(dāng)屬性的值與 Map 中 key 相同的時(shí)候,我們可以把對(duì)應(yīng) key 的 value 取出來(lái)并賦值給屬性:

class Member(private val map: Map<String, Any>) {

    val name: String by map
    val age: Int by map
    val dob: Long by map

    override fun toString(): String {
        return "Member(name='$name', age=$age, dob=$dob)"
    }

}

使用:

        val member = Member(mapOf("name" to "guanyu", "age" to 36, Pair("dob", 1234567890L)))
        YYLogUtils.w("member:$member")

打印的日志:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

但是需要注意的是,map 中的 key 名字必須要和屬性的名字一致才行,否則委托后運(yùn)行解析時(shí)會(huì)拋出 NoSuchElementException 異常提示。

例如我們?cè)?Member 對(duì)象中加入一個(gè)并不存在的 address 屬性,再次運(yùn)行就會(huì)報(bào)錯(cuò)。

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

而我們把 Int 的 age 屬性賦值給為字符串也會(huì)報(bào)類(lèi)型轉(zhuǎn)換異常:

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

所以一定要一一對(duì)應(yīng)才行哦,我怎么感覺(jué)有一點(diǎn) TypeScript 結(jié)構(gòu)賦值的那味道 - - !

Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么

以上就是“Android開(kāi)發(fā)之Kotlin委托的原理與使用方法是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI