使用 by 进行委托
本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 46 课,共 57 课。
委托是指一个对象把工作交给另一个对象,而不是通过继承来实现。Kotlin 直接支持委托:class Polite(private val inner: Greeter) : Greeter by inner 会将每次调用转发给 inner,从而实现 Greeter,类只需重写它要修改的内容:
interface Greeter {
fun hello(name: String): String
fun bye(name: String): String
}
class English : Greeter {
override fun hello(name: String) = "Hello, $name"
override fun bye(name: String) = "Bye, $name"
}
class Polite(private val inner: Greeter) : Greeter by inner {
override fun hello(name: String) = inner.hello(name) + ", pleased to meet you"
}在 main 中:
val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))输出:
Hello, Ada, pleased to meet you
Bye, Ada委托是继承的另一种选择。它适用于实现类不可继承的接口,并且委托对象可以是在程序运行时选择的任意对象。这个集合会统计每次 add 调用,并将其他所有操作转发给普通的 MutableSet:
class CountingSet<T>(private val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner {
var attempts = 0
private set
override fun add(element: T): Boolean {
attempts++
return inner.add(element)
}
}在 main 中:
val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("jvm")
tags.add("kotlin")
println("${tags.size} tags after ${tags.attempts} adds, contains jvm: ${"jvm" in tags}")输出:
2 tags after 3 adds, contains jvm: true委托属性会使用 by 将属性的 getter 和 setter 交给另一个对象。第 2 章中的 lazy 就是这样一种委托,映射也可以作为委托:每个属性都会读取与自己同名的条目:
class User(values: Map<String, Any?>) {
val name: String by values
val age: Int by values
}在 main 中:
val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")输出:
Ada is 36你可以编写自己的属性委托:一个带有 operator fun getValue 的类,还可以为 var 添加 setValue。Kotlin 会在每次读取和写入属性时调用它们。这个委托会去掉每次赋值文本两端的空格,并统计修改次数:
import kotlin.reflect.KProperty
class Trimmed {
private var value = ""
var changes = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
value = newValue.trim()
changes++
}
}
class Form {
val nameField = Trimmed()
var name: String by nameField
}在 main 中:
val form = Form()
form.name = " Ada "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")输出:
[Ada L.] after 2 changes挑战
简单接口 Storage(包含 put、get 和 keys)及其实现 MapStorage 已提供。请使用类委托编写两个装饰器,每个装饰器只重写 put:LoggingStorage(inner) 会在其只读 log 列表中记录 put a=1,并转发调用;LimitedStorage(inner, max) 会在已有 max 个键时拒绝添加新键(返回 false),但仍允许更新已有键。其他所有内容都通过 by 转发。
提供的代码会将 MapStorage 包装在 LoggingStorage 中;如果第一行是正数限制值,还会再将其包装在 LimitedStorage 中。代码会运行 put a 1、get a 和 keys 命令,输出 ok、refused、对应的值或 missing,以及键列表,最后输出 log: put a=1, ...。
请将代码写入 LoggingStorage.kt、LimitedStorage.kt、Storage.kt 和 MapStorage.kt。Main.kt 中包含提供的输入/输出代码,不能编辑。
亲手试试
fun main() {
// 提供的输入输出代码:保持原样。
val input = generateSequence(::readLine).toList()
val base = MapStorage()
val logging = LoggingStorage(base)
val limit = input[0].toInt()
val storage: Storage = if (limit > 0) LimitedStorage(logging, limit) else logging
for (cmd in input.drop(1)) {
val p = cmd.split(" ")
when (p[0]) {
"put" -> println(if (storage.put(p[1], p[2])) "ok" else "refused")
"get" -> println(storage.get(p[1]) ?: "missing")
else -> println(storage.keys())
}
}
println("log: " + logging.log.joinToString(", ").ifEmpty { "empty" })
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Kotlin 在线编辑器