Android開發(fā)之Kotlin委托的原理與使用詳解
前言
在設(shè)計(jì)模式中,委托模式(Delegate Pattern)與代理模式都是我們常用的設(shè)計(jì)模式(Proxy Pattern),兩者非常的相似,又有細(xì)小的區(qū)分。
委托模式中,委托對(duì)象和被委托對(duì)象都是同一類型的對(duì)象,委托對(duì)象將任務(wù)委托給被委托對(duì)象來完成。委托模式可以用于實(shí)現(xiàn)事件監(jiān)聽器、回調(diào)函數(shù)等功能。
代理模式中,代理對(duì)象與被代理對(duì)象是兩種不同的對(duì)象,代理對(duì)象代表被代理對(duì)象的功能,代理對(duì)象可以控制客戶對(duì)被代理對(duì)象的訪問。代理模式可以用于實(shí)現(xiàn)遠(yuǎn)程代理、虛擬代理、安全代理等功能。
以類的委托與代理來舉例,委托對(duì)象和被委托對(duì)象都實(shí)現(xiàn)了同一個(gè)接口或繼承了同一個(gè)類,委托對(duì)象將任務(wù)委托給被委托對(duì)象來完成。代理模式中,代理對(duì)象與被代理對(duì)象實(shí)現(xiàn)了同一個(gè)接口或繼承了同一個(gè)類,代理對(duì)象代表被代理對(duì)象,客戶端通過代理對(duì)象來訪問被代理對(duì)象。
兩者的區(qū)別:
他們雖然都有同一個(gè)接口,主要區(qū)別在于委托模式中委托對(duì)象和被委托對(duì)象是同一類型的對(duì)象,而代理模式中代理對(duì)象與被代理對(duì)象是兩種不同的對(duì)象。總的來說,委托模式是為了將方法的實(shí)現(xiàn)交給其他類去完成,而代理模式則是為了控制對(duì)象的訪問,并在訪問前后進(jìn)行額外的操作。
而我們常用的委托模式怎么使用?在 Java 語言中需要我們手動(dòng)的實(shí)現(xiàn),而在 Kotlin 語言中直接通過關(guān)鍵字 by 就可以實(shí)現(xiàn)委托,其實(shí)現(xiàn)更加優(yōu)雅、簡潔了。
我們?cè)陂_發(fā)一個(gè) Android 應(yīng)用中,常用到的委托分為:
- 接口/類的委托
- 屬性的委托
- 結(jié)合lazy的延遲委托
- 觀察者的委托
- Map數(shù)據(jù)的委托
下面我們就一起看看不同種類的委托使用以及在 Android 常見的一些場(chǎng)景中的使用。
一、接口/類委托
我們可以選擇使用接口來實(shí)現(xiàn)類似的效果,也可以直接傳參,當(dāng)然接口的方式更加的靈活,比如我們這里就以接口比如我定義一個(gè)攻擊與防御的行為接口:
interface IUserAction { fun attack() fun defense() }
定義了用戶的行為,有攻擊和防御兩種操作!接下來我們就定義一個(gè)默認(rèn)的實(shí)現(xiàn)類:
class UserActionImpl : IUserAction { override fun attack() { YYLogUtils.w("默認(rèn)操作-開始執(zhí)行攻擊") } override fun defense() { YYLogUtils.w("默認(rèn)操作-開始執(zhí)行防御") } }
都是很簡單的代碼,我們定義一些默認(rèn)的操作,如果任意類想擁有攻擊和防御的能力就直接實(shí)現(xiàn)這個(gè)接口,如果想自定義攻擊和防御則重寫對(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)也可以重寫部分的操作:
class UserDelegate3(private val action: IUserAction) : IUserAction by action { override fun attack() { YYLogUtils.w("UserDelegate3 - 只重寫了攻擊") } }
那么使用起來就是這樣的:
val actionImpl = UserActionImpl() UserDelegate1(actionImpl).run { attack() defense() } UserDelegate2(actionImpl).run { attack() defense() } UserDelegate3(actionImpl).run { attack() defense() }
打印日志如下:
其實(shí)在 Android 源碼中也有不少委托的使用,例如生命周期的 Lifecycle 委托:
Lifecycle 通過委托機(jī)制實(shí)現(xiàn)其功能。具體來說,組件可以將自己的生命周期狀態(tài)委托給 LifecycleOwner 對(duì)象,LifecycleOwner 對(duì)象則負(fù)責(zé)管理這些組件的生命周期。
例如,在一個(gè) Activity 中,我們可以通過將 Activity 對(duì)象作為 LifecycleOwner 對(duì)象,并將該對(duì)象傳遞給需要注冊(cè)生命周期的組件,從而實(shí)現(xiàn)組件的生命周期管理。 頁面可以使用 getLifecycle() 方法來獲取它所依賴的 LifecycleOwner 對(duì)象的 Lifecycle 實(shí)例,并在需要時(shí)將自身的生命周期狀態(tài)委托給該 Lifecycle 實(shí)例。
通過這種委托機(jī)制,Lifecycle 實(shí)現(xiàn)了一種方便的方式來管理組件的生命周期,避免了手動(dòng)管理生命周期帶來的麻煩和錯(cuò)誤。
class AnimUtil private constructor() : DefaultLifecycleObserver { ... private fun addLoopLifecycleObserver() { mOwner?.lifecycle?.addObserver(this) } // 退出頁面的時(shí)候釋放資源 override fun onDestroy(owner: LifecycleOwner) { mAnim?.cancel() destory() } }
除此之外委托還特別適用于一些可配置的功能,比如 Resutl-Api 的封裝,如果當(dāng)前頁面需要開啟 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 }
使用起來我們直接用默認(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") } } }
這樣是不是就非常簡單了呢?具體如何使用封裝 Result Launcher 可以看看我去年的文章 【傳送門】
二、屬性委托
除了類與接口對(duì)象的委托,我們還常用于屬性的委托。
我知道了!這么弄就行了。
private val textStr by "123"
哎?怎么報(bào)錯(cuò)了?其實(shí)不是這么用的。
屬性委托和類委托一樣,屬性的委托其實(shí)是對(duì)屬性的 set/get 方法的委托。
需要我們把 set/get 方法委托給 setValue/getValue 方法,因此被委托類(真實(shí)類)需要提供 setValue/getValue 方法,val屬性只需要提供 getValue 方法。
我們修改代碼如下:
private val textStr by TextDelegate() class TextDelegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "我是賦值給與的文本" } }
打印的結(jié)果:
而我們定義一個(gè)可讀寫的屬性則可以
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"
打印則如下:
為了怕大家寫錯(cuò),我們其實(shí)可以用接口來限制,只讀的和讀寫的屬性,我們分別可以用 ReadOnlyProperty 與 ReadWriteProperty 來限制:
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)。
我們的屬性除了委托給類去實(shí)現(xiàn),同時(shí)也能委托給其他屬性(Kotlin 1.4+)來實(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è)委托類,而且還還在初始化時(shí)就創(chuàng)建了委托類的實(shí)例對(duì)象,算起來其實(shí)性能并不好。
所以屬性的委托不要濫用,如果要用,可以選擇委托現(xiàn)成的其他屬性來完成,或者使用延遲委托Lazy實(shí)現(xiàn),或者使用更簡單的方式實(shí)現(xiàn):
private val industryName: String get() { return "abc123" }
對(duì)于只讀的屬性,這種方式也是我們常見的使用方式。
三、延遲委托
如果說使用類來實(shí)現(xiàn)委托不那么好的話,其實(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ì)初始化,一旦初始化之后就可以直接獲取到值。
日志打印:
它的內(nèi)部其實(shí)也是使用的是類的委托實(shí)現(xiàn)。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
最終的實(shí)現(xiàn)是由 SynchronizedLazyImpl 類生成并實(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)初始化過了,就直接返回 value ,否則表明沒有初始化過,調(diào)用initializer方法,也就是 lazy 的 lambda 表達(dá)式返回屬性的賦值。
跟我們自己實(shí)現(xiàn)類的委托類似,也是實(shí)現(xiàn)了getValue方法。只是多了判斷是否初始化的一些相關(guān)邏輯。
lazy的參數(shù)分為三種類型:
- SYNCHRONIZED:添加同步鎖,使lazy延遲初始化線程安全
- PUBLICATION:初始化的lambda表達(dá)式,可以在同一時(shí)間多次調(diào)用,但是只有第一次的返回值作為初始化值
- NONE:沒有同步鎖,非線程安全
默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步鎖的(synchronized),是可以保證線程安全的,但是如果不需要線程安全和減少性能花銷可以可以使用 lazy(LazyThreadSafetyMode.NONE){}
即可。
四、觀察者委托
除了對(duì)屬性的值進(jìn)行委托,我們甚至還能對(duì)觀察到這個(gè)變化過程:
使用 observable 委托監(jiān)聽值的變化:
var values: String by Delegates.observable("默認(rèn)值") { property, oldValue, newValue -> YYLogUtils.w("打印值: $oldValue -> $newValue ") } values = "第一次修改" values = "第二次修改" values = "第三次修改"
打?。?/p>
我們還能使用 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 值覺得是否使用新值,比如上述的例子就是當(dāng)新值大于老值的時(shí)候才賦值。那么打印的日志就是如下:
雖然這種方式我們并不常用,一般我們都是使用類似 Flow 之類的工具在源頭就處理了邏輯,使用這種方式我們就可以在屬性的賦值過程中進(jìn)行攔截了。在一些特定的場(chǎng)景下還是有用的。
五、Map委托
我們的屬性不止可以使用類的委托,延遲的委托,觀察的委托,還能委托Map來進(jìn)行賦值。
當(dāng)屬性的值與 Map 中 key 相同的時(shí)候,我們可以把對(duì)應(yīng) key 的 value 取出來并賦值給屬性:
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")
打印的日志:
但是需要注意的是,map 中的 key 名字必須要和屬性的名字一致才行,否則委托后運(yùn)行解析時(shí)會(huì)拋出 NoSuchElementException 異常提示。
例如我們?cè)?Member 對(duì)象中加入一個(gè)并不存在的 address 屬性,再次運(yùn)行就會(huì)報(bào)錯(cuò)。
而我們把 Int 的 age 屬性賦值給為字符串也會(huì)報(bào)類型轉(zhuǎn)換異常:
所以一定要一一對(duì)應(yīng)才行哦,我怎么感覺有一點(diǎn) TypeScript 結(jié)構(gòu)賦值的那味道 - - !
總結(jié)
委托雖好不要濫用。委托畢竟還是中間多了一個(gè)委托類,如果沒必要可以直接賦值實(shí)現(xiàn),而不需要多一個(gè)中間類占用內(nèi)存。
我們可以通過接口委托來實(shí)現(xiàn)一些可選的配置。通過委托類實(shí)現(xiàn)屬性的監(jiān)聽與賦值。可以減少一些模板代碼,達(dá)到低耦合高內(nèi)聚的效果,可以提高程序的可維護(hù)性、可擴(kuò)展性和可重用性。
對(duì)于屬性的類委托,我們可以將屬性的讀取和寫入操作委托給另一個(gè)對(duì)象,或者另一個(gè)屬性,或者使用延遲委托來推遲對(duì)象的創(chuàng)建直到第一次訪問。
對(duì)于 map 的委托,我們需要仔細(xì)對(duì)應(yīng)屬性與 key 的一致性。以免出現(xiàn)錯(cuò)誤,這是運(yùn)行時(shí)的錯(cuò)誤,有可能出現(xiàn)在生產(chǎn)環(huán)境上的。
那么大家都是怎么使用的呢?有沒有更好的方式呢?或者你有遇到的坑也都可以在評(píng)論區(qū)交流一下,大家可以互相學(xué)習(xí)進(jìn)步。如有本文有一些錯(cuò)漏的地方,希望同學(xué)們可以指出。
到此這篇關(guān)于Android開發(fā)之Kotlin委托的原理與使用詳解的文章就介紹到這了,更多相關(guān)Android Kotlin委托內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中dip、dp、sp、pt和px的區(qū)別詳解
本篇文章是對(duì)Android中dip、dp、sp、pt和px的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Android?ViewStub使用方法學(xué)習(xí)
這篇文章主要為大家介紹了Android?ViewStub使用方法學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11ScrollView滾動(dòng)條顏色的設(shè)置方法
ScrollView滾動(dòng)條顏色的設(shè)置方法,需要的朋友可以參考一下2013-06-06Android實(shí)現(xiàn)修改狀態(tài)欄背景、字體和圖標(biāo)顏色的方法
本篇文章主要介紹了Android實(shí)現(xiàn)修改狀態(tài)欄背景、字體和圖標(biāo)顏色的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android自定義標(biāo)尺滑動(dòng)選擇值效果
這篇文章主要為大家詳細(xì)介紹了Android自定義標(biāo)尺滑動(dòng)選擇值效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09python gstreamer實(shí)現(xiàn)視頻快進(jìn)/快退/循環(huán)播放功能
這篇文章主要介紹了python gstreamer 實(shí)現(xiàn)視頻快進(jìn)/快退/循環(huán)播放功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03詳解dex優(yōu)化對(duì)Arouter查找路徑的影響
dex簡單說就是優(yōu)化后的android版.exe。每個(gè)apk安裝包里都有。相對(duì)于PC上的java虛擬機(jī)能運(yùn)行.class,android上的Davlik虛擬機(jī)能運(yùn)行.dex。本文將著重介紹dex優(yōu)化對(duì)Arouter查找路徑的影響2021-06-06Android 使用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)及輪播效果
ViewPager是一個(gè)常用的Android組件,不過通常我們使用ViewPager的時(shí)候不能實(shí)現(xiàn)左右無限循環(huán)滑動(dòng),在滑到邊界的時(shí)候會(huì)看到一個(gè)不能翻頁的動(dòng)畫,可能影響用戶體驗(yàn),接下來通過本文給大家介紹Android 使用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)及輪播效果,一起看看吧2017-02-02