<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在設計模式中,委託模式(Delegate Pattern)與代理模式都是我們常用的設計模式(Proxy Pattern),兩者非常的相似,又有細小的區分。
委託模式中,委託物件和被委託物件都是同一型別的物件,委託物件將任務委託給被委託物件來完成。委託模式可以用於實現事件監聽器、回撥函數等功能。
代理模式中,代理物件與被代理物件是兩種不同的物件,代理物件代表被代理物件的功能,代理物件可以控制客戶對被代理物件的存取。代理模式可以用於實現遠端代理、虛擬代理、安全代理等功能。
以類的委託與代理來舉例,委託物件和被委託物件都實現了同一個介面或繼承了同一個類,委託物件將任務委託給被委託物件來完成。代理模式中,代理物件與被代理物件實現了同一個介面或繼承了同一個類,代理物件代表被代理物件,使用者端通過代理物件來存取被代理物件。
兩者的區別:
他們雖然都有同一個介面,主要區別在於委託模式中委託物件和被委託物件是同一型別的物件,而代理模式中代理物件與被代理物件是兩種不同的物件。總的來說,委託模式是為了將方法的實現交給其他類去完成,而代理模式則是為了控制物件的存取,並在存取前後進行額外的操作。
而我們常用的委託模式怎麼使用?在 Java 語言中需要我們手動的實現,而在 Kotlin 語言中直接通過關鍵字 by 就可以實現委託,其實現更加優雅、簡潔了。
我們在開發一個 Android 應用中,常用到的委託分為:
下面我們就一起看看不同種類的委託使用以及在 Android 常見的一些場景中的使用。
我們可以選擇使用介面來實現類似的效果,也可以直接傳參,當然介面的方式更加的靈活,比如我們這裡就以介面比如我定義一個攻擊與防禦的行為介面:
interface IUserAction { fun attack() fun defense() }
定義了使用者的行為,有攻擊和防禦兩種操作!接下來我們就定義一個預設的實現類:
class UserActionImpl : IUserAction { override fun attack() { YYLogUtils.w("預設操作-開始執行攻擊") } override fun defense() { YYLogUtils.w("預設操作-開始執行防禦") } }
都是很簡單的程式碼,我們定義一些預設的操作,如果任意類想擁有攻擊和防禦的能力就直接實現這個介面,如果想自定義攻擊和防禦則重寫對應的方法即可。
如果使用 Java 的方式實現委託,大致程式碼如下:
class UserDelegate1(private val action: IUserAction) : IUserAction { override fun attack() { YYLogUtils.w("UserDelegate1-需要自己實現攻擊") } override fun defense() { YYLogUtils.w("UserDelegate1-需要自己實現防禦") } }
如果使用 Kotlin 的方式實現則是:
class UserDelegate2(private val action: IUserAction) : IUserAction by action
如果 Kotlin 的實現不想預設的實現也可以重寫部分的操作:
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() }
列印紀錄檔如下:
其實在 Android 原始碼中也有不少委託的使用,例如生命週期的 Lifecycle 委託:
Lifecycle 通過委託機制實現其功能。具體來說,元件可以將自己的生命週期狀態委託給 LifecycleOwner 物件,LifecycleOwner 物件則負責管理這些元件的生命週期。
例如,在一個 Activity 中,我們可以通過將 Activity 物件作為 LifecycleOwner 物件,並將該物件傳遞給需要註冊生命週期的元件,從而實現元件的生命週期管理。 頁面可以使用 getLifecycle() 方法來獲取它所依賴的 LifecycleOwner 物件的 Lifecycle 範例,並在需要時將自身的生命週期狀態委託給該 Lifecycle 範例。
通過這種委託機制,Lifecycle 實現了一種方便的方式來管理元件的生命週期,避免了手動管理生命週期帶來的麻煩和錯誤。
class AnimUtil private constructor() : DefaultLifecycleObserver { ... private fun addLoopLifecycleObserver() { mOwner?.lifecycle?.addObserver(this) } // 退出頁面的時候釋放資源 override fun onDestroy(owner: LifecycleOwner) { mAnim?.cancel() destory() } }
除此之外委託還特別適用於一些可設定的功能,比如 Resutl-Api 的封裝,如果當前頁面需要開啟 startActivityForResult 的功能,就實現這個介面,不需要這個功能就不實現介面,達到可設定的效果。
/** * 定義是否需要SAFLauncher */ interface ISAFLauncher { fun <T : ActivityResultCaller> T.initLauncher() fun getLauncher(): GetSAFLauncher? }
由於程式碼是固定的實現,目標Activity也不需要重新實現,我們只需要實現預設的實現即可:
class SAFLauncher : ISAFLauncher { private var safLauncher: GetSAFLauncher? = null override fun <T : ActivityResultCaller> T.initLauncher() { safLauncher = GetSAFLauncher(this) } override fun getLauncher(): GetSAFLauncher? = safLauncher }
使用起來我們直接用預設的實現即可:
class DemoActivity : BaseActivity, ISAFLauncher by SAFLauncher() { override fun init() { initLauncher() // 實現了介面還需要初始化Launcher } fun gotoOtherPage() { //使用 Result Launcher 的方式啟動,並獲取到返回值 getLauncher()?.launch<DemoCircleActivity> { result -> val result = result.data?.getStringExtra("text") toast("收到返回的資料:$result") } } }
這樣是不是就非常簡單了呢?具體如何使用封裝 Result Launcher 可以看看我去年的文章 【傳送門】
除了類與介面物件的委託,我們還常用於屬性的委託。
我知道了!這麼弄就行了。
private val textStr by "123"
哎?怎麼報錯了?其實不是這麼用的。
屬性委託和類委託一樣,屬性的委託其實是對屬性的 set/get 方法的委託。
需要我們把 set/get 方法委託給 setValue/getValue 方法,因此被委託類(真實類)需要提供 setValue/getValue 方法,val屬性只需要提供 getValue 方法。
我們修改程式碼如下:
private val textStr by TextDelegate() class TextDelegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "我是賦值給與的文字" } }
列印的結果:
而我們定義一個可讀寫的屬性則可以
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("設定的值為:$value") } } YYLogUtils.w("textStr:$textStr") textStr = "abc123"
列印則如下:
為了怕大家寫錯,我們其實可以用介面來限制,唯讀的和讀寫的屬性,我們分別可以用 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("設定的值為:$value") } }
那麼實現的方式和上面自己實現的效果是一樣的。如果要使用屬性委託可以選用這種介面限制的方式實現。
我們的屬性除了委託給類去實現,同時也能委託給其他屬性(Kotlin 1.4+)來實現,例如:
private var textStr by TextDelegate2() private var textStr2 by this::textStr
其實是內部委託了物件的 get 和 set 函數。相對委託物件而言效能更好一些。而委託物件去實現,不僅增加了一個委託類,而且還還在初始化時就建立了委託類的範例物件,算起來其實效能並不好。
所以屬性的委託不要濫用,如果要用,可以選擇委託現成的其他屬性來完成,或者使用延遲委託Lazy實現,或者使用更簡單的方式實現:
private val industryName: String get() { return "abc123" }
對於唯讀的屬性,這種方式也是我們常見的使用方式。
如果說使用類來實現委託不那麼好的話,其實我們可以使用延遲委託。延遲關鍵字 lazy 接收一個 lambda 表示式,最後一行代表返回值給被推脫的屬性。
預設的 Lazy 實現:
val name: String by lazy { YYLogUtils.w("第一次呼叫初始化") "abc123" } YYLogUtils.w(name) YYLogUtils.w(name) YYLogUtils.w(name)
只有在第一次使用此屬性的時候才會初始化,一旦初始化之後就可以直接獲取到值。
紀錄檔列印:
它的內部其實也是使用的是類的委託實現。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
最終的實現是由 SynchronizedLazyImpl 類生成並實現的:
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 則表明已經初始化過了,就直接返回 value ,否則表明沒有初始化過,呼叫initializer方法,也就是 lazy 的 lambda 表示式返回屬性的賦值。
跟我們自己實現類的委託類似,也是實現了getValue方法。只是多了判斷是否初始化的一些相關邏輯。
lazy的引數分為三種型別:
預設情況下,對於 lazy 屬性的求值是同步鎖的(synchronized),是可以保證執行緒安全的,但是如果不需要執行緒安全和減少效能花銷可以可以使用 lazy(LazyThreadSafetyMode.NONE){}
即可。
除了對屬性的值進行委託,我們甚至還能對觀察到這個變化過程:
使用 observable 委託監聽值的變化:
var values: String by Delegates.observable("預設值") { property, oldValue, newValue -> YYLogUtils.w("列印值: $oldValue -> $newValue ") } values = "第一次修改" values = "第二次修改" values = "第三次修改"
列印:
我們還能使用 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 值覺得是否使用新值,比如上述的例子就是當新值大於老值的時候才賦值。那麼列印的紀錄檔就是如下:
雖然這種方式我們並不常用,一般我們都是使用類似 Flow 之類的工具在源頭就處理了邏輯,使用這種方式我們就可以在屬性的賦值過程中進行攔截了。在一些特定的場景下還是有用的。
我們的屬性不止可以使用類的委託,延遲的委託,觀察的委託,還能委託Map來進行賦值。
當屬性的值與 Map 中 key 相同的時候,我們可以把對應 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 名字必須要和屬性的名字一致才行,否則委託後執行解析時會丟擲 NoSuchElementException 異常提示。
例如我們在 Member 物件中加入一個並不存在的 address 屬性,再次執行就會報錯。
而我們把 Int 的 age 屬性賦值給為字串也會報型別轉換異常:
所以一定要一一對應才行哦,我怎麼感覺有一點 TypeScript 結構賦值的那味道 - - !
委託雖好不要濫用。委託畢竟還是中間多了一個委託類,如果沒必要可以直接賦值實現,而不需要多一箇中間類佔用記憶體。
我們可以通過介面委託來實現一些可選的設定。通過委託類實現屬性的監聽與賦值。可以減少一些模板程式碼,達到低耦合高內聚的效果,可以提高程式的可維護性、可延伸性和可重用性。
對於屬性的類委託,我們可以將屬性的讀取和寫入操作委託給另一個物件,或者另一個屬性,或者使用延遲委託來推遲物件的建立直到第一次存取。
對於 map 的委託,我們需要仔細對應屬性與 key 的一致性。以免出現錯誤,這是執行時的錯誤,有可能出現在生產環境上的。
那麼大家都是怎麼使用的呢?有沒有更好的方式呢?或者你有遇到的坑也都可以在評論區交流一下,大家可以互相學習進步。如有本文有一些錯漏的地方,希望同學們可以指出。
到此這篇關於Android開發之Kotlin委託的原理與使用詳解的文章就介紹到這了,更多相關Android Kotlin委託內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45