類的委託
委託模式可以很好的替代實現繼承,kotlin本身支持需要零樣板代碼,一個類Derived 可以繼承Base並委託它所有的public 方法到一個指定的類:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print() // prints 10
}
這個在父類後面的by指定b存儲爲Derived的內部對象,編譯器會生成Base的所有方法寄於到b上。
屬性的委託
屬性的委託指的一個類的某個屬性的值不是在類中直接進行定義,而是由某個類的方法來進行 setter 和 getter。默認屬性委託都是線程安全的。屬性委託適合那些屬性的需要複雜的計算但是計算過程可以被重用的場合。
定義一個被委託的類
該類需要包含 getValue() 方法和 setValue() 方法,且參數 thisRef 爲進行委託的類的對象,prop 爲進行委託的屬性的對象。
“`
class Delegate {
private var message = "Default Message"
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
return "${prop.name} = $message from $thisRef"
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
message = value
}
} “`
定義包含屬性委託的類
class Example {
var msg: String by Delegate()
}
訪問該屬性
val e = Example()
println(e.msg) // msg = Default Message
e.msg = "New Message"
println(e.msg) // msg = New Message
在使用屬性委託時,被委託的類的方法(即接收者)的返回值必須與委託的屬性相同或者其父類。
標準委託
Kotlin 的標準庫中已經內置了很多工廠方法來實現屬性的委託。
lazy
lazy 用於進行惰性加載,即第一次使用時才執行初始化的操作。
Observable
observable 可以用於實現觀察者模式。
定義包含被委託的屬性的類
Delegates.observable 接收三個參數:包含委託的屬性的元數據的 KProperty 對象,舊值,新值。
class User {
var name: String by Delegates.observable("<init value>") {
prop, old, new ->
if (old != new) {
println("\\$old -> $new")
}
}}
訪問該屬性
val user = User()
user.name = "first" // <init value> -> first
user.name = "first"
user.name = "second" // first -> second
也可以使用 vetoable 代替 observable,該方法擁有布爾類型的返回值,返回 false 的話可以取消對該屬性的修改。
在以上 User 中定義一個新屬性。
var age: Int by Delegates.vetoable(0) {
prop, old, new ->
println("$old -> $new")
if (new < 20) true else false
}
訪問屬性:
user.age = 10 // 0 -> 10
println(user.age) // 10
user.age = 20 // 10 -> 20
println(user.age) // 20
NotNull
notNull 適用於那些無法在初始化階段就確定屬性值的場合。
class Foo {
var notNullBar: String by Delegates.notNull<String>()
}foo.notNullBar = "bar"
println(foo.notNullBar)
需要注意,如果屬性在賦值前就被訪問的話則會拋出異常。
以 Map 形式保存屬性的值
Kotlin 中有一種特別的委託,可以以 Map 作爲一個類的構造方法的參數,訪問該類的屬性就是訪問該 Map 的鍵值對。這種做法非常類似 Groovy 中的帶名構造方法。
要實現這一功能需要得意於 Kotlin 內置的屬性的擴展方法 kotlin.properties.getValue
例:
import kotlin.properties.getValue
class Person(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
可以像普通類一樣訪問其各個屬性:
val person = Person(mapOf(
"name" to "John",
"age" to 25
))
println(person.name)
println(person.age)
對於可變值,可以使用 MutableMap 代替,並導入擴展方法 kotlin.properties.setValue。
import kotlin.properties.setValue
class MutablePerson(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}