委託模式是軟件設計模式中的一項基本技巧。在委託模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另一個對象來處理。
Kotlin 直接支持委託模式,更加優雅,簡潔。Kotlin 通過關鍵字 by 實現委託。
類委託
類的委託即一個類中定義的方法實際是調用另一個類的對象的方法來實現的。
以下實例中派生類 Derived 繼承了接口 Base 所有方法,並且委託一個傳入的 Base 類的對象來執行這些方法。
在 Derived 聲明中,by 子句表示,將 b 保存在 Derived 的對象實例內部,而且編譯器將會生成繼承自 Base 接口的所有方法, 並將調用轉發給 b。
// 創建接口
interface Base {
fun print()
}
// 實現此接口的被委託的類
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通過關鍵字 by 建立委託類
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 輸出 10
}
屬性委託
屬性委託指的是一個類的某個屬性值不是在類中直接進行定義,而是將其託付給一個代理類,從而實現對該類的屬性統一管理。
屬性委託語法格式:
val/var <屬性名>: <類型> by <表達式>
- var/val:屬性類型(可變/只讀)
- 屬性名:屬性名稱
- 類型:屬性的數據類型
- 表達式:委託代理類
by 關鍵字之後的表達式就是委託, 屬性的 get() 方法(以及set() 方法)將被委託給這個對象的 getValue() 和 setValue() 方法。屬性委託不必實現任何接口, 但必須提供 getValue() 函數(對於 var屬性,還需要 setValue() 函數)。
定義一個被委託的類
該類需要包含 getValue() 方法和 setValue() 方法,且參數 thisRef 爲進行委託的類的對象,prop 爲進行委託的屬性的對象。
import kotlin.reflect.KProperty
// 定義包含屬性委託的類
class Example {
var p: String by Delegate()
}
// 委託的類
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 這裏委託了 ${property.name} 屬性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 屬性賦值爲 $value")
}
}
fun main(args: Array<String>) {
val e = Example()
println(e.p) // 訪問該屬性,調用 getValue() 函數
e.p = "Runoob" // 調用 setValue() 函數
println(e.p)
}
延遲屬性 Lazy
lazy() 是一個函數, 接受一個 Lambda 表達式作爲參數, 返回一個 Lazy <T> 實例的函數,返回的實例可以作爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。
val lazyValue: String by lazy {
println("computed!") // 第一次調用輸出,第二次調用不執行
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // 第一次執行,執行兩次輸出表達式
println(lazyValue) // 第二次執行,只輸出返回值
}
執行輸出結果:
computed!
Hello
Hello
可觀察屬性 Observable
observable 可以用於實現觀察者模式。
Delegates.observable() 函數接受兩個參數: 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler)。
在屬性賦值後會執行事件的響應器(handler),它有三個參數:被賦值的屬性、舊值和新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("舊值:$old -> 新值:$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "第一次賦值"
user.name = "第二次賦值"
}
執行輸出結果:
舊值:初始值 -> 新值:第一次賦值
舊值:第一次賦值 -> 新值:第二次賦值
把屬性儲存在映射中
一個常見的用例是在一個映射(map)裏存儲屬性的值。 這經常出現在像解析 JSON 或者做其他"動態"事情的應用中。 在這種情況下,你可以使用映射實例自身作爲委託來實現委託屬性。
class Site(val map: Map<String, Any?>) {
val name: String by map
val url: String by map
}
fun main(args: Array<String>) {
// 構造函數接受一個映射參數
val site = Site(mapOf(
"name" to "zhongjh",
"url" to "www.zhongjh.com"
))
// 讀取映射值
println(site.name)
println(site.url)
}
執行輸出結果:
zhongjh
www.zhongjh.com
如果使用 var 屬性,需要把 Map 換成 MutableMap:
class Site(val map: MutableMap<String, Any?>) {
val name: String by map
val url: String by map
}
fun main(args: Array<String>) {
var map:MutableMap<String, Any?> = mutableMapOf(
"name" to "zhongjh",
"url" to "www.zhongjh.com"
)
val site = Site(map)
println(site.name)
println(site.url)
println("--------------")
map.put("name", "Google")
map.put("url", "www.google.com")
println(site.name)
println(site.url)
}
執行輸出結果:
zhongjh
www.zhongjh.com
--------------
Google
www.google.com
Not Null
notNull 適用於那些無法在初始化階段就確定屬性值的場合。
需要注意,如果屬性在賦值前就被訪問的話則會拋出異常。
class Foo {
var notNullBar: String by Delegates.notNull()
}
fun main() {
val foo = Foo()
// foo.notNullBar // by notNull後,這樣是會報異常的
foo.notNullBar = "bar"
}
局部委託屬性
你可以將局部變量聲明爲委託屬性。
fun main() {
val zhongjh : String by Delegates.notNull()
}
屬性委託要求
對於只讀屬性(也就是說val屬性), 它的委託必須提供一個名爲getValue()的函數。該函數接受以下參數:
- thisRef —— 必須與屬性所有者類型(對於擴展屬性——指被擴展的類型)相同或者是它的超類型
- property —— 必須是類型 KProperty<*> 或其超類型
這個函數必須返回與屬性相同的類型(或其子類型)。
對於一個值可變(mutable)屬性(也就是說,var 屬性),除 getValue()函數之外,它的委託還必須 另外再提供一個名爲setValue()的函數, 這個函數接受以下參數:
- property —— 必須是類型 KProperty<*> 或其超類型
- new value —— 必須和屬性同類型或者是它的超類型。
下面可以看到屬性委託要求代碼,var
class Property {
var p: String by Delegate()
}
// 委託的類
class Delegate {
operator fun getValue(property1: Property, property: KProperty<*>): String {
return "$property1, 這裏委託了 ${property.name} 屬性"
}
operator fun setValue(property1: Property, property: KProperty<*>, s: String) {
println("$property1 的 ${property.name} 屬性賦值爲 $s")
}
}
val
class PropertyVal {
val p: String by DelegateVal()
}
// 委託的類
class DelegateVal {
operator fun getValue(propertyVal: PropertyVal, property: KProperty<*>): String {
return ""
}
}
翻譯規則
在每個委託屬性的實現的背後,Kotlin 編譯器都會生成輔助屬性並委託給它。 例如,對於屬性 prop,生成隱藏屬性 prop$delegate,而訪問器的代碼只是簡單地委託給這個附加屬性:
class C {
var prop: Type by MyDelegate()
}
// 這段是由編譯器生成的相應代碼:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
Kotlin 編譯器在參數中提供了關於 prop 的所有必要信息:第一個參數 this 引用到外部類 C 的實例而 this::prop 是 KProperty 類型的反射對象,該對象描述 prop 自身。
提供委託
這個有點複雜,等博主用在實際應用中理解透徹後單獨講解這方面