Kotlin設(shè)計(jì)模式之委托模式使用方法詳解
1、組合由于繼承
眾所周知,在面向?qū)ο笳Z(yǔ)言中,繼承是關(guān)鍵特性之一。這意味著您可以將一些功能和抽象放入基類(lèi)中。其他類(lèi)可以從基類(lèi)繼承因此獲得繼承的功能。這稱(chēng)為“Is-a”關(guān)系。作為教科書(shū)的示例,我們可以想象Shape類(lèi)(基類(lèi))和rectangle、circle等為派生類(lèi)。
過(guò)去,這種建模方式被濫用,導(dǎo)致設(shè)計(jì)不佳。例如,長(zhǎng)繼承鏈用于對(duì)象添加增量功能。作為一個(gè)過(guò)于夸張的示例,請(qǐng)考慮一下類(lèi)設(shè)置。我們想要?jiǎng)?chuàng)建“Animal”并實(shí)現(xiàn)它們的移動(dòng)和進(jìn)食方式。一種簡(jiǎn)單的方法是如下圖所示實(shí)現(xiàn)它。很明顯,這不會(huì)擴(kuò)展。

另一方面,下圖更傾向于組合而不是繼承。該類(lèi)Animal實(shí)現(xiàn)接口IMoving和IEating。然而它沒(méi)有實(shí)際的實(shí)現(xiàn)代碼,它將調(diào)用委托給Walking和具體實(shí)現(xiàn)MeatEating。正如我們所看到的,這種設(shè)計(jì)更加靈活,因?yàn)榭梢詫?shí)現(xiàn)Moving和Eating而不用修改Animal類(lèi)。

委派繼承是實(shí)現(xiàn)SOLID設(shè)計(jì)模式中的開(kāi)放-關(guān)閉原則的一種方式。
2、接口委托
如何在Kotlin中創(chuàng)建委托? Kotlin提供了原生功能來(lái)實(shí)現(xiàn)委托模式,而無(wú)需編寫(xiě)任何樣板代碼。這僅適用于接口(即抽象類(lèi))。接口實(shí)現(xiàn)IWalking如下代碼所示。
// 標(biāo)準(zhǔn)委托
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()
}正如我們所看到的,第一部分代碼需要實(shí)現(xiàn)接口方法。在正確的版本中,編譯器會(huì)為您執(zhí)行此操作。當(dāng)界面具有多種功能時(shí),效果會(huì)更加顯著。在派生類(lèi)類(lèi)型的超類(lèi)型的列表中使用by-子句表示可移動(dòng)的信息將在IMoving對(duì)象的內(nèi)部存儲(chǔ),編譯器將生成所有將轉(zhuǎn)發(fā)到IMoving的接口方法。
2.1、覆蓋接口成員
如果需要重寫(xiě)接口的特定成員函數(shù),只需在派生類(lèi)中編寫(xiě)該函數(shù)并添加關(guān)鍵字override即可。編譯器將使用重寫(xiě)方法的新規(guī)范。在下面的實(shí)例中,我們創(chuàng)建了該方法的新版本move。
// By Delegate的重寫(xiě)功能
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、多個(gè)接口/繼承
為了完成上面的例子,我們必須實(shí)現(xiàn)所有接口并將所有函數(shù)調(diào)用委托給成員變量。我們可以通過(guò)將與上一節(jié)相同的方法應(yīng)用于所有繼承的接口來(lái)做到這一點(diǎn)。
// 介個(gè)接口委托
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ù)簽名
如果您需要實(shí)現(xiàn)多個(gè)聲明了相同方法的接口,則會(huì)出現(xiàn)特殊情況。在這種情況下,委托是不明確的,編譯器會(huì)給出一個(gè)錯(cuò)誤,例如“Class 'xxx' must override public open fun doSomething(): Unit defined in Animal because it inherits many implementation of it”。您需要顯示實(shí)現(xiàn)(或重寫(xiě))此函數(shù)并手動(dòng)委托它。

2.4、在運(yùn)行時(shí)替換委托
通常需要在運(yùn)行時(shí)更改委托。這通常用在策略或狀態(tài)模式中。(目前)Kotlin不支持在使用by關(guān)鍵字進(jìn)行注入時(shí)更改委托。
以下代碼具有誤導(dǎo)性,因?yàn)樗蛴∫韵聝?nèi)容:
Walking Walking Running@xxx
//在運(yùn)行時(shí)更改委托
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提供了一些很好的功能來(lái)說(shuō)實(shí)現(xiàn)委托屬性。它向類(lèi)屬性添加了一些常見(jiàn)功能。您可以在庫(kù)中創(chuàng)建一些屬性(例如Lazy)并使用此功能包裝您的類(lèi)成員。
要了解其工作原理,您需要了解每個(gè)成員變量在幕后提供getValue和setValue。屬性委托不需要實(shí)現(xiàn)接口,但需要為每種類(lèi)型提供這些函數(shù)。正如您所看到的,屬性委托是通過(guò)對(duì)象/函數(shù)。
在下面的示例,我們提供了一個(gè)委托,該委托在每次訪問(wèn)時(shí)都會(huì)打印消息。
3.1、只讀委托
任何屬性委托必須至少為所委托的值類(lèi)型實(shí)現(xiàn)以下函數(shù)。這type T取決于被包裝的值類(lèi)型。
// Read requited delegate function
operator fun getValue(example: Any, property: KProperty<<*>): T {
return // value of T
}作為示例,我們將使用與Kotlin參考頁(yè)面略有不同的代碼。我們的委托只能用于字符串類(lèi)型,并且僅返回空字符串。這段代碼毫無(wú)用處,但它顯示了只讀屬性的用法。
// 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ě)委托
為了使用上述委托進(jìn)行讀寫(xiě)實(shí)現(xiàn),我們還必須實(shí)現(xiàn)以下函數(shù)。請(qǐng)注意,我們有一個(gè)字符串值類(lèi)型。因此我們寫(xiě)了s: String。您需要將其調(diào)整為您想要使用的類(lèi)型。
// Write required delegate function
operator fun setValue(example: Any, property: KProperty<*>, s: String) {
}我們上面的例子現(xiàn)在可以實(shí)現(xiàn)所需的功能了。我們將引入一個(gè)類(lèi)變量cached來(lái)保存實(shí)際值。
// 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、實(shí)現(xiàn)接口作為擴(kuò)展
另一種方法是將getValue()和setValue()函數(shù)實(shí)現(xiàn)為類(lèi)的擴(kuò)展函數(shù)Delegate。如果該類(lèi)不在您的源代碼管理中,這很有用。
// 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、通用委托定義
明顯的缺點(diǎn)是此代碼僅適用于字符串類(lèi)型的對(duì)象。如果我們想將它用于其他類(lèi)型,我們必須使用泛型類(lèi)。我們將向您展示如何更改上述示例以使其兼容所有類(lèi)型。
// 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、匿名對(duì)象委托
Kotlin還提供了一種創(chuàng)建匿名對(duì)象委托而無(wú)需創(chuàng)建新類(lèi)的方法。這是由于接口ReadOnlyProperty和ReadWriteProperty標(biāo)準(zhǔn)庫(kù)而起作用的。這些接口將為ReadOnlyProperty提供getValue函數(shù)。ReadWriteProperty通過(guò)添加setValue()函數(shù),擴(kuò)展了ReadOnlyProperty。
在上面的示例,委托屬性是通過(guò)函數(shù)調(diào)用創(chuàng)建為匿名對(duì)象的。
// 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、委托給另一個(gè)屬性
從一個(gè)屬性委托給另一個(gè)屬性可能是提供向后兼容性的一個(gè)非常有用的技巧。想象一下,在類(lèi)的1.0版本中,您有一些公共成員函數(shù)。但在他們的一生中,名字發(fā)生了變化。為了不破壞客戶端,我們可以將舊的成員函數(shù)標(biāo)記為deprecated(通過(guò)注解)并將其調(diào)用轉(zhuǎn)發(fā)到新的實(shí)現(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)驗(yàn),在某些情況下,此代碼無(wú)法編譯,并出現(xiàn)錯(cuò)誤代碼:
知道有一個(gè)開(kāi)放的錯(cuò)誤(LINK),我們希望它能盡快修復(fù)!
4、委托示例和用例
Kotlin為常見(jiàn)問(wèn)題提供了一些預(yù)先實(shí)現(xiàn)的解決方案。請(qǐng)?jiān)L問(wèn)KotlinLang頁(yè)面查看完整列表。對(duì)于當(dāng)前的實(shí)現(xiàn),我們將向您展示幾個(gè)示例。
4.1、通過(guò)observable
可以通過(guò)使用可觀察委托來(lái)獲得基本功能。當(dāng)值改變時(shí)它提供回調(diào)。這樣做得好處是您可以訪問(wèn)新的和以前的值。要使用可觀察委托,您必須指定初始值。
// 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、通過(guò)vetoable
vetoable有點(diǎn)類(lèi)似觀察者委托。然而,回調(diào)函數(shù)可用于撤銷(xiāo)修改。請(qǐng)注意,回調(diào)必須返回布爾值。如果成功,則為true;如果失敗,則為false。在我們的書(shū)籍示例中,我們檢查書(shū)籍的信內(nèi)容是否為null或?yàn)榭铡T谶@種情況下,它被認(rèn)為是錯(cuò)誤的,我們想要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、通過(guò)Lazy
Kotlin提供了變量延遲計(jì)算的現(xiàn)有實(shí)現(xiàn)。一個(gè)常見(jiàn)的用例是需要花費(fèi)大量時(shí)間來(lái)初始化但在開(kāi)始時(shí)不會(huì)直接使用的成員變臉。例如,考慮一個(gè)Book類(lèi),它獲取一個(gè)表示整個(gè)文本的字符串。書(shū)籍類(lèi)擁有另一個(gè)對(duì)書(shū)籍進(jìn)行昂貴的后處理的類(lèi)。開(kāi)發(fā)人員決定讓這個(gè)示例變得Lazy,因?yàn)樗⒉皇强偙徽{(diào)用。
Lazy屬性附加到analyzer類(lèi)。運(yùn)行代碼時(shí),它首先會(huì)打印出書(shū)籍已創(chuàng)建,然后analyzer成員變量已實(shí)例化。
// 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、通過(guò)Not Null
返回具有非null值的讀/寫(xiě)屬性的屬性委托,該屬性不是在對(duì)象構(gòu)造期間而是在稍后的時(shí)間初始化。由于它是在稍后時(shí)間點(diǎn)創(chuàng)建的,因此與延遲初始化有關(guān)。然而,一個(gè)很大的缺點(diǎn)是沒(méi)有本地方法可以知道該值是否已初始化。它需要一些樣板代碼才能保存。我們的十本里將類(lèi)似于以下代碼。請(qǐng)注意,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
您可以使用委托對(duì)應(yīng)用程序日志記錄庫(kù)的訪問(wèn)。在帶有惰性的函數(shù)中,這是為每個(gè)需要的類(lèi)提供對(duì)記錄器的訪問(wèn)的最慣用的方法。
// 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設(shè)計(jì)模式之委托模式使用方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin委托模式使用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Gradle Build Error:Some file crunching failed, see l
這篇文章主要介紹了Android Gradle Build Error:Some file crunching failed, see logs for details的快速解決方法的相關(guān)資料,需要的朋友可以參考下2016-10-10
Android實(shí)現(xiàn)伸縮彈力分布菜單效果的示例
本文介紹下在Android中實(shí)現(xiàn)伸縮彈力分布菜單效果。這種效果比較炫酷,有需要的朋友可以參考一下。2016-10-10
Android共享元素動(dòng)畫(huà)效果顯示問(wèn)題解決
什么是共享元素呢?可以理解為當(dāng)頁(yè)面跳轉(zhuǎn)是,看起來(lái)一個(gè)View屬于界面A又屬于界面B,下面這篇文章主要給大家介紹了關(guān)于Android共享元素動(dòng)畫(huà)效果顯示問(wèn)題的相關(guān)資料,需要的朋友可以參考下2022-02-02
Flutter實(shí)現(xiàn)滾動(dòng)選擇數(shù)字
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)滾動(dòng)選擇數(shù)字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Android實(shí)現(xiàn)底部導(dǎo)航欄效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部導(dǎo)航欄效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Android用文件存儲(chǔ)數(shù)據(jù)的方法
這篇文章主要為大家詳細(xì)介紹了Android用文件存儲(chǔ)數(shù)據(jù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android中invalidate()和postInvalidate() 的區(qū)別及使用方法
Android中實(shí)現(xiàn)view的更新有兩組方法,一組是invalidate,另一組是postInvalidate,其中前者是在UI線程自身中使用,而后者在非UI線程中使用。本文給大家介紹Android中invalidate()和postInvalidate() 的區(qū)別及使用方法,感興趣的朋友一起學(xué)習(xí)吧2016-05-05

