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

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

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

2.4、在運(yùn)行時替換委托
通常需要在運(yùn)行時更改委托。這通常用在策略或狀態(tài)模式中。(目前)Kotlin不支持在使用by關(guān)鍵字進(jìn)行注入時更改委托。
以下代碼具有誤導(dǎo)性,因?yàn)樗蛴∫韵聝?nèi)容:
Walking Walking Running@xxx
//在運(yùn)行時更改委托
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提供了一些很好的功能來說實(shí)現(xiàn)委托屬性。它向類屬性添加了一些常見功能。您可以在庫中創(chuàng)建一些屬性(例如Lazy)并使用此功能包裝您的類成員。
要了解其工作原理,您需要了解每個成員變量在幕后提供getValue和setValue。屬性委托不需要實(shí)現(xiàn)接口,但需要為每種類型提供這些函數(shù)。正如您所看到的,屬性委托是通過對象/函數(shù)。
在下面的示例,我們提供了一個委托,該委托在每次訪問時都會打印消息。
3.1、只讀委托
任何屬性委托必須至少為所委托的值類型實(shí)現(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、讀/寫委托
為了使用上述委托進(jìn)行讀寫實(shí)現(xiàn),我們還必須實(shí)現(xiàn)以下函數(shù)。請注意,我們有一個字符串值類型。因此我們寫了s: String。您需要將其調(diào)整為您想要使用的類型。
// Write required delegate function
operator fun setValue(example: Any, property: KProperty<*>, s: String) {
}我們上面的例子現(xiàn)在可以實(shí)現(xiàn)所需的功能了。我們將引入一個類變量cached來保存實(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)為類的擴(kuò)展函數(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、通用委托定義
明顯的缺點(diǎn)是此代碼僅適用于字符串類型的對象。如果我們想將它用于其他類型,我們必須使用泛型類。我們將向您展示如何更改上述示例以使其兼容所有類型。
// 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標(biāo)準(zhǔn)庫而起作用的。這些接口將為ReadOnlyProperty提供getValue函數(shù)。ReadWriteProperty通過添加setValue()函數(shù),擴(kuò)展了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ù)標(biāo)記為deprecated(通過注解)并將其調(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),在某些情況下,此代碼無法編譯,并出現(xiàn)錯誤代碼:
知道有一個開放的錯誤(LINK),我們希望它能盡快修復(fù)!
4、委托示例和用例
Kotlin為常見問題提供了一些預(yù)先實(shí)現(xiàn)的解決方案。請訪問KotlinLang頁面查看完整列表。對于當(dāng)前的實(shí)現(xiàn),我們將向您展示幾個示例。
4.1、通過observable
可以通過使用可觀察委托來獲得基本功能。當(dāng)值改變時它提供回調(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ǎn)類似觀察者委托。然而,回調(diào)函數(shù)可用于撤銷修改。請注意,回調(diào)必須返回布爾值。如果成功,則為true;如果失敗,則為false。在我們的書籍示例中,我們檢查書籍的信內(nèi)容是否為null或?yàn)榭?。在這種情況下,它被認(rèn)為是錯誤的,我們想要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)有實(shí)現(xiàn)。一個常見的用例是需要花費(fèi)大量時間來初始化但在開始時不會直接使用的成員變臉。例如,考慮一個Book類,它獲取一個表示整個文本的字符串。書籍類擁有另一個對書籍進(jìn)行昂貴的后處理的類。開發(fā)人員決定讓這個示例變得Lazy,因?yàn)樗⒉皇强偙徽{(diào)用。
Lazy屬性附加到analyzer類。運(yùn)行代碼時,它首先會打印出書籍已創(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、通過Not Null
返回具有非null值的讀/寫屬性的屬性委托,該屬性不是在對象構(gòu)造期間而是在稍后的時間初始化。由于它是在稍后時間點(diǎn)創(chuàng)建的,因此與延遲初始化有關(guān)。然而,一個很大的缺點(diǎ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
您可以使用委托對應(yīng)用程序日志記錄庫的訪問。在帶有惰性的函數(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設(shè)計模式之委托模式使用方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin委托模式使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android獲取清單文件中的meta-data,解決碰到數(shù)值為null的問題
這篇文章主要介紹了Android獲取清單文件中的meta-data,解決碰到數(shù)值為null的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android編程實(shí)現(xiàn)的自定義彈窗(PopupWindow)功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)的自定義彈窗(PopupWindow)功能,結(jié)合簡單實(shí)例形式分析了Android自定義彈窗實(shí)現(xiàn)方法與相關(guān)注意事項,需要的朋友可以參考下2017-03-03
android開發(fā)教程之framework增加字符串資源和圖片等resource資源
這篇文章主要介紹了android開發(fā)中framework增加字符串資源和圖片等resource資源方法,需要的朋友可以參考下2014-02-02
Android 實(shí)現(xiàn)圓角圖片的簡單實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)圓角圖片的簡單實(shí)例的相關(guān)資料,Android 圓角圖片的實(shí)現(xiàn)形式,包括用第三方、也有系統(tǒng),需要的朋友可以參考下2017-07-07
android項目從Eclipse遷移到Android studio中常見問題解決方法
android項目從Eclipse遷移到Android studio中經(jīng)常會遇到一些問題,本文提供了Android studio使用中常見問題解決方法2018-03-03

