委托模式:类委托、属性委托、标准委托、Map委托
委托模式,说白了就是「我不想干这活,交给别人干」。在 Kotlin 里,这个模式被语言层面直接支持了,用起来特别顺手。我个人非常喜欢这个特性,因为它能帮我们写出更干净、更灵活的代码。
你想想看,传统 Java 里要实现委托,你得手动写一堆转发方法,又臭又长。Kotlin 直接用 by 关键字搞定,编译器帮你生成那些模板代码。嗯,这就是 Kotlin 的务实之处。
类委托:把实现交给别人
类委托的意思是:一个类实现某个接口,但实际干活的是另一个对象。我举个实际例子——
假设我们有个 Logger 接口:
interface Logger {
fun log(message: String)
fun logError(message: String)
}
传统做法是每个实现类都自己写一遍方法体。但用委托,你可以这样:
class ConsoleLogger : Logger {
override fun log(message: String) = println("INFO: $message")
override fun logError(message: String) = println("ERROR: $message")
}
class FileLogger : Logger {
override fun log(message: String) { /* 写入文件 */ }
override fun logError(message: String) { /* 写入文件 */ }
}
// 委托给 ConsoleLogger
class AppLogger(logger: Logger) : Logger by logger
fun main() {
val logger = AppLogger(ConsoleLogger())
logger.log("启动成功") // 实际调用 ConsoleLogger.log()
}
这里 AppLogger 自己一行代码都没写,所有接口方法都委托给了传入的 logger 对象。我在项目中经常用这个模式来做「装饰器」——比如给日志加时间戳,只需要在委托类外面包一层:
class TimestampLogger(private val wrapped: Logger) : Logger by wrapped {
override fun log(message: String) {
wrapped.log("[${System.currentTimeMillis()}] $message")
}
}
属性委托:懒加载、观察者、拦截器
属性委托是 Kotlin 委托模式的精髓。一个属性的 getter/setter 可以委托给另一个对象来处理。说白了,就是「这个属性的读写逻辑,我交给别人管」。
语法很简单:
class Example {
var name: String by Delegate()
}
这里的 Delegate 需要实现 getValue 和 setValue 操作符。不过日常开发中,我们很少自己写委托类,因为 Kotlin 标准库已经提供了几个非常实用的标准委托。
标准委托:lazy、observable、vetoable
这三个是我用得最多的标准委托,几乎每个项目都会碰到。
lazy:懒加载
lazy 委托用于「第一次访问时才初始化」的场景。我经常用它来加载配置、创建重量级对象。
class ConfigManager {
val config: Config by lazy {
println("加载配置...")
loadConfigFromFile() // 耗时操作
}
}
fun main() {
val manager = ConfigManager()
println("对象已创建") // 此时 config 还没加载
println(manager.config) // 第一次访问,触发加载
println(manager.config) // 第二次访问,直接返回缓存值
}
输出结果:
对象已创建
加载配置...
Config(...)
Config(...)
lazy 默认是线程安全的。如果你确定只在单线程中使用,可以传 LazyThreadSafetyMode.NONE 提升性能。
我曾经在项目中遇到过一个坑:用 lazy 委托的属性,如果初始化代码里抛异常,第二次访问时会重新尝试初始化。嗯,这个行为其实挺合理的,但如果你没注意到,可能会觉得是 bug。
observable:观察属性变化
observable 委托让你在属性值变化时收到回调。我常用它来做数据绑定、日志记录。
class UserViewModel {
var userName: String by Delegates.observable("初始值") { prop, old, new ->
println("${prop.name} 从 $old 变为 $new")
// 可以在这里更新 UI、发送事件等
}
}
fun main() {
val vm = UserViewModel()
vm.userName = "Alice" // 输出:userName 从 初始值 变为 Alice
vm.userName = "Bob" // 输出:userName 从 Alice 变为 Bob
}
注意,observable 是在值已经改变之后才回调。如果你想在赋值之前做拦截,需要用 vetoable。
vetoable:带否决权的观察者
vetoable 和 observable 很像,但多了一个「否决权」。回调返回 true 表示允许修改,返回 false 表示拒绝修改。
class TemperatureController {
var temperature: Int by Delegates.vetoable(25) { _, old, new ->
// 温度不能超过 40 度
if (new > 40) {
println("温度 $new 过高,拒绝修改")
false
} else {
println("温度从 $old 变为 $new")
true
}
}
}
fun main() {
val controller = TemperatureController()
controller.temperature = 30 // 成功:温度从 25 变为 30
controller.temperature = 50 // 失败:温度 50 过高,拒绝修改
println(controller.temperature) // 输出 30,没有被改成 50
}
vetoable 做输入拦截,结果发现它只能阻止属性赋值,但不能阻止对象内部状态的改变。如果你需要更复杂的验证逻辑,建议结合 require 或自定义 setter 一起使用。
Map 委托:动态属性
Map 委托是我觉得 Kotlin 最巧妙的设计之一。它允许你把一个类的属性映射到 Map 的键值对上。说白了,就是「对象的属性就是 Map 的 key,属性的值就是 Map 的 value」。
class Person(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
val email: String? by map
}
fun main() {
val data = mapOf(
"name" to "张三",
"age" to 28,
"email" to "zhangsan@example.com"
)
val person = Person(data)
println(person.name) // 张三
println(person.age) // 28
println(person.email) // zhangsan@example.com
}
这个模式在解析 JSON、处理配置数据时特别有用。你想想看,从 API 拿到的数据通常就是 Map 结构,用 Map 委托可以直接映射成类型安全的对象,省去了手动解析的麻烦。
如果你需要可变属性,用 MutableMap 即可:
class MutablePerson(map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
fun main() {
val data = mutableMapOf("name" to "李四", "age" to 30)
val person = MutablePerson(data)
person.age = 31
println(data["age"]) // 31,Map 也跟着变了
}
知识体系总览
下面这张图帮你理清委托模式的整体脉络:
总结一下
委托模式在 Kotlin 里不是锦上添花,而是实实在在能减少样板代码、提升代码质量的手段。我个人建议你从 lazy 和 observable 开始用起,这两个最常用也最容易上手。等你熟悉了,再尝试自定义委托和 Map 委托。
嗯,委托模式就讲到这里。记住一句话:能用委托解决的问题,就别自己手写模板代码。Kotlin 帮你省下的时间,拿去喝杯咖啡不好吗?
公众号:蓝海资料掘金营,微信 deep3321