Kotlin設計模式之委托模式使用方法詳解
1、組合由于繼承
眾所周知,在面向?qū)ο笳Z言中,繼承是關(guān)鍵特性之一。這意味著您可以將一些功能和抽象放入基類中。其他類可以從基類繼承因此獲得繼承的功能。這稱為“Is-a”關(guān)系。作為教科書的示例,我們可以想象Shape類(基類)和rectangle、circle等為派生類。
過去,這種建模方式被濫用,導致設計不佳。例如,長繼承鏈用于對象添加增量功能。作為一個過于夸張的示例,請考慮一下類設置。我們想要創(chuàng)建“Animal”并實現(xiàn)它們的移動和進食方式。一種簡單的方法是如下圖所示實現(xiàn)它。很明顯,這不會擴展。
另一方面,下圖更傾向于組合而不是繼承。該類Animal實現(xiàn)接口IMoving
和IEating
。然而它沒有實際的實現(xiàn)代碼,它將調(diào)用委托給Walking
和具體實現(xiàn)MeatEating
。正如我們所看到的,這種設計更加靈活,因為可以實現(xiàn)Moving
和Eating
而不用修改Animal類。
委派繼承是實現(xiàn)SOLID設計模式中的開放-關(guān)閉原則的一種方式。
2、接口委托
如何在Kotlin中創(chuàng)建委托? Kotlin提供了原生功能來實現(xiàn)委托模式,而無需編寫任何樣板代碼。這僅適用于接口(即抽象類)。接口實現(xiàn)IWalking
如下代碼所示。
// 標準委托 interface IMoving { fun move() } class Walking: IMoving { override fun move() { println("Walking") } } class Animal(private val movable: IMoving): IMoving { override fun move() { movable.move() } } fim main() { var movable = Walking() var animal = Animal(movable) animal.move() }
// 內(nèi)置委托 interface IMoving { fun move() } class Walking: IMoving { override fun move() { println("Walking") } } class Animal(movable: IMoving): IMoving by movable { } fun main() { var walking = Walking() var animal = Animal(walking) animal.move() }
正如我們所看到的,第一部分代碼需要實現(xiàn)接口方法。在正確的版本中,編譯器會為您執(zhí)行此操作。當界面具有多種功能時,效果會更加顯著。在派生類類型的超類型的列表中使用by
-子句表示可移動的信息將在IMoving
對象的內(nèi)部存儲,編譯器將生成所有將轉(zhuǎn)發(fā)到IMoving
的接口方法。
2.1、覆蓋接口成員
如果需要重寫接口的特定成員函數(shù),只需在派生類中編寫該函數(shù)并添加關(guān)鍵字override
即可。編譯器將使用重寫方法的新規(guī)范。在下面的實例中,我們創(chuàng)建了該方法的新版本move
。
// By Delegate的重寫功能 class Animal(movable: IMoving): IMoving by movable { override fun move() { println("Something else") } } fun main() { var walking = Walking() var animal = Animal(walking) animal.move() }
2.2、多個接口/繼承
為了完成上面的例子,我們必須實現(xiàn)所有接口并將所有函數(shù)調(diào)用委托給成員變量。我們可以通過將與上一節(jié)相同的方法應用于所有繼承的接口來做到這一點。
// 介個接口委托 interface IMoving { fun move() } class Walking: IMoving { override fun move() { println("Walking") } } interface IEating { fun eat() } class MeatEater: IEating { override fun eat() { println("Eat meat") } } class Animal(movable: IMoving, eating: IEating): IMoving by movable, IEading by eating { } fun main() { var walking = Walking() var eating = MeatEater() var animal = Animal(walking, eating) animal.move() animal.eat() }
2.3、相同的函數(shù)簽名
如果您需要實現(xiàn)多個聲明了相同方法的接口,則會出現(xiàn)特殊情況。在這種情況下,委托是不明確的,編譯器會給出一個錯誤,例如“Class 'xxx' must override public open fun doSomething(): Unit defined in Animal because it inherits many implementation of it
”。您需要顯示實現(xiàn)(或重寫)此函數(shù)并手動委托它。
2.4、在運行時替換委托
通常需要在運行時更改委托。這通常用在策略或狀態(tài)模式中。(目前)Kotlin不支持在使用by
關(guān)鍵字進行注入時更改委托。
以下代碼具有誤導性,因為它打印以下內(nèi)容:
Walking Walking Running@xxx
//在運行時更改委托 interface IMoving { fun move() } class Running: IMoving { override fun move() { println("Running") } } class Walking: IMoving { override fun move() { println("Walking") } } class Animal(var movable: IMoving): IMoving by movable { } fun main() { var walking = Walking() var animal = Animal(walking) animal.move() var running = Running() animal.movable = running animal.move() println(animal.movable) }
3、屬性委托
什么是屬性委托? Kotlin提供了一些很好的功能來說實現(xiàn)委托屬性。它向類屬性添加了一些常見功能。您可以在庫中創(chuàng)建一些屬性(例如Lazy)并使用此功能包裝您的類成員。
要了解其工作原理,您需要了解每個成員變量在幕后提供getValue
和setValue
。屬性委托不需要實現(xiàn)接口,但需要為每種類型提供這些函數(shù)。正如您所看到的,屬性委托是通過對象/函數(shù)。
在下面的示例,我們提供了一個委托,該委托在每次訪問時都會打印消息。
3.1、只讀委托
任何屬性委托必須至少為所委托的值類型實現(xiàn)以下函數(shù)。這type T
取決于被包裝的值類型。
// Read requited delegate function operator fun getValue(example: Any, property: KProperty<<*>): T { return // value of T }
作為示例,我們將使用與Kotlin參考頁面略有不同的代碼。我們的委托只能用于字符串類型,并且僅返回空字符串。這段代碼毫無用處,但它顯示了只讀屬性的用法。
// Example Read only delegate class Example { val p: String by Delegate() } class Delegate { operator fun getValue(example: Any, property: KProperty<*>): String { return "" } }
3.2、讀/寫委托
為了使用上述委托進行讀寫實現(xiàn),我們還必須實現(xiàn)以下函數(shù)。請注意,我們有一個字符串值類型。因此我們寫了s: String
。您需要將其調(diào)整為您想要使用的類型。
// Write required delegate function operator fun setValue(example: Any, property: KProperty<*>, s: String) { }
我們上面的例子現(xiàn)在可以實現(xiàn)所需的功能了。我們將引入一個類變量cached
來保存實際值。
// Example Read/Write delegate class Example { var p: String by Delegate() } class Delegate { var cached = "" operator fun getValue(example: Any, property: KProperty<*>): String { return cached } operator fun setValue(example: Any, property: KProperty<*>, s: String) { cached = s } }
3.3、實現(xiàn)接口作為擴展
另一種方法是將getValue()
和setValue()
函數(shù)實現(xiàn)為類的擴展函數(shù)Delegate
。如果該類不在您的源代碼管理中,這很有用。
// Delegated property as extension function class Example { val string: String by Delegate() } class Delegate { } operator fun Delegate.getValue(example: Any, propety: KProperty<*>): String { return "" }
3.4、通用委托定義
明顯的缺點是此代碼僅適用于字符串類型的對象。如果我們想將它用于其他類型,我們必須使用泛型類。我們將向您展示如何更改上述示例以使其兼容所有類型。
// Generic class for Delegate class Example { var string: String by Delegate("hello") var int: Int by Delegate(3) } class Delegate<T>(var cached: T) { operator fun getValue(example: Any, property: KProperty<*>): T { return cached } operator fun setValue(example: Any, property: KProperty<*>, s: T) { cached = s } }
3.5、匿名對象委托
Kotlin還提供了一種創(chuàng)建匿名對象委托而無需創(chuàng)建新類的方法。這是由于接口ReadOnlyProperty
和ReadWriteProperty
標準庫而起作用的。這些接口將為ReadOnlyProperty
提供getValue
函數(shù)。ReadWriteProperty
通過添加setValue()
函數(shù),擴展了ReadOnlyProperty
。
在上面的示例,委托屬性是通過函數(shù)調(diào)用創(chuàng)建為匿名對象的。
// Anonymous objects delegates class Example { var string: String by delegate() } fun delegate(): ReadWriteProperty<Any?, String> = object: ReadWriteProperty<any?, String> { var curValue = "" override fun getValue<thisRef: Any?, property: KProperty<*>): String = curValue override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { curValue = value } }
3.6、委托給另一個屬性
從一個屬性委托給另一個屬性可能是提供向后兼容性的一個非常有用的技巧。想象一下,在類的1.0版本中,您有一些公共成員函數(shù)。但在他們的一生中,名字發(fā)生了變化。為了不破壞客戶端,我們可以將舊的成員函數(shù)標記為deprecated
(通過注解)并將其調(diào)用轉(zhuǎn)發(fā)到新的實現(xiàn)。
// Delegate to other member class Example { var newName: String = "" @Deprecated("Use 'newName' instead", ReplaceWith("newName")) var oldName: String by this::newName } fun main() { val example = Example() example.oldName = "hello" println(example.newName) }
我們的經(jīng)驗,在某些情況下,此代碼無法編譯,并出現(xiàn)錯誤代碼:
知道有一個開放的錯誤(LINK),我們希望它能盡快修復!
4、委托示例和用例
Kotlin為常見問題提供了一些預先實現(xiàn)的解決方案。請訪問KotlinLang頁面查看完整列表。對于當前的實現(xiàn),我們將向您展示幾個示例。
4.1、通過observable
可以通過使用可觀察委托來獲得基本功能。當值改變時它提供回調(diào)。這樣做得好處是您可以訪問新的和以前的值。要使用可觀察委托,您必須指定初始值。
// by observable class Book { var content: Strign by obserable("") { property, oldValue, newValue -> println("Book changed") } } fun main() { val book = Book() book.content = "New content" }
4.2、通過vetoable
vetoable有點類似觀察者委托。然而,回調(diào)函數(shù)可用于撤銷修改。請注意,回調(diào)必須返回布爾值。如果成功,則為true;如果失敗,則為false。在我們的書籍示例中,我們檢查書籍的信內(nèi)容是否為null或為空。在這種情況下,它被認為是錯誤的,我們想要vetoable該更改。
// vetoable class Book { var content: String by vetoable("") { property, oldValue, newValue -> !newValue.isNullOrEmpty() } } fun main() { val book = Book() book.content = "New content" println(book.content) book.content = "" println(book.content) }
4.3、通過Lazy
Kotlin提供了變量延遲計算的現(xiàn)有實現(xiàn)。一個常見的用例是需要花費大量時間來初始化但在開始時不會直接使用的成員變臉。例如,考慮一個Book類,它獲取一個表示整個文本的字符串。書籍類擁有另一個對書籍進行昂貴的后處理的類。開發(fā)人員決定讓這個示例變得Lazy,因為它并不是總被調(diào)用。
Lazy屬性附加到analyzer
類。運行代碼時,它首先會打印出書籍已創(chuàng)建,然后analyzer
成員變量已實例化。
// by lazy class Book(private val rawText: String) { private val analyser: Analyser by lazy { Analyser() } fun analyse() { analyser.doSomething() } } class Analyser { init { println("Init analyser class") } fun doSomething() { print("DoSomething") } } fun main() { val rawText = "MyBook" val book = Book(rawText) println("Book is created") book.analyse() }
4.4、通過Not Null
返回具有非null值的讀/寫屬性的屬性委托,該屬性不是在對象構(gòu)造期間而是在稍后的時間初始化。由于它是在稍后時間點創(chuàng)建的,因此與延遲初始化有關(guān)。然而,一個很大的缺點是沒有本地方法可以知道該值是否已初始化。它需要一些樣板代碼才能保存。我們的十本里將類似于以下代碼。請注意,Lazy
要好得多。
// By Delegates.NotNull class Analyser { init { println("Init analyser class") } fun doSomething() { println("DoSomething") } } class Book(private val rawText: String) { var analyser: Analyser by Delegates.notNull() fun analyse() { analyser = Analyser() analyser.doSomething() } } fun main() { val rawText = "MyBook" val book = Book(rawText) println("Book is created") book.analyse() }
4.5、Logging
您可以使用委托對應用程序日志記錄庫的訪問。在帶有惰性的函數(shù)中,這是為每個需要的類提供對記錄器的訪問的最慣用的方法。
// Idiomatic logging access fun <R: Any> R.logger(): Lazy<Logger> { return lazy { Logger.getLogger(unwrapCompainionClass(this.javaClass).name) } } class Something { val LOG by logger() fun foo() { LOG.info("Hello from Something") } }
以上就是Kotlin設計模式之委托模式使用方法詳解的詳細內(nèi)容,更多關(guān)于Kotlin委托模式使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android獲取清單文件中的meta-data,解決碰到數(shù)值為null的問題
這篇文章主要介紹了Android獲取清單文件中的meta-data,解決碰到數(shù)值為null的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android編程實現(xiàn)的自定義彈窗(PopupWindow)功能示例
這篇文章主要介紹了Android編程實現(xiàn)的自定義彈窗(PopupWindow)功能,結(jié)合簡單實例形式分析了Android自定義彈窗實現(xiàn)方法與相關(guān)注意事項,需要的朋友可以參考下2017-03-03android開發(fā)教程之framework增加字符串資源和圖片等resource資源
這篇文章主要介紹了android開發(fā)中framework增加字符串資源和圖片等resource資源方法,需要的朋友可以參考下2014-02-02android項目從Eclipse遷移到Android studio中常見問題解決方法
android項目從Eclipse遷移到Android studio中經(jīng)常會遇到一些問題,本文提供了Android studio使用中常見問題解決方法2018-03-03