一、为什么需要委托?------ 一个真实的痛点
1.1 场景:你接手了一个遗留项目
假设你接手了一个电商项目,里面有一个 OrderService 类,它实现了 Logger 接口:
kotlin
interface Logger {
fun info(message: String)
fun warn(message: String)
fun error(message: String)
}
class OrderService : Logger {
override fun info(message: String) {
println("[INFO] $message")
}
override fun warn(message: String) {
println("[WARN] $message")
}
override fun error(message: String) {
println("[ERROR] $message")
}
// 业务方法
fun createOrder(userId: String, productId: String) {
info("开始创建订单...")
// ... 业务逻辑
info("订单创建成功")
}
}
现在,产品经理提出了一个新需求:所有日志都要加上时间戳。
1.2 传统方案的困境
方案一:直接修改 ------ 侵入性强
kotlin
class OrderService : Logger {
override fun info(message: String) {
println("[INFO] [${System.currentTimeMillis()}] $message") // 修改
}
override fun warn(message: String) {
println("[WARN] [${System.currentTimeMillis()}] $message") // 修改
}
override fun error(message: String) {
println("[ERROR] [${System.currentTimeMillis()}] $message") // 修改
}
// ...
}
问题 :如果 UserService、ProductService 也需要同样的功能,你要在每个类里重复修改。
方案二:继承 ------ 不够灵活
kotlin
open class TimestampLogger : Logger {
override fun info(message: String) {
println("[INFO] [${System.currentTimeMillis()}] $message")
}
override fun warn(message: String) {
println("[WARN] [${System.currentTimeMillis()}] $message")
}
override fun error(message: String) {
println("[ERROR] [${System.currentTimeMillis()}] $message")
}
}
class OrderService : TimestampLogger() // 继承
class UserService : TimestampLogger() // 继承
问题:
- 如果
OrderService已经继承了其他类怎么办?(单继承限制) - 如果只想给
info加时间戳,warn和error保持原样呢?
方案三:组合 + 手动转发 ------ 样板代码太多
kotlin
class OrderService(private val logger: Logger) : Logger {
override fun info(message: String) {
println("[INFO] [${System.currentTimeMillis()}] $message") // 自定义
}
override fun warn(message: String) {
logger.warn(message) // 纯转发,样板代码
}
override fun error(message: String) {
logger.error(message) // 纯转发,样板代码
}
// ...
}
问题 :如果 Logger 有 10 个方法,你要写 9 个纯转发的样板方法。
1.3 Kotlin 委托的解决方案
kotlin
class OrderService(logger: Logger) : Logger by logger {
override fun info(message: String) {
println("[INFO] [${System.currentTimeMillis()}] $message") // 只写你想改的
}
// warn() 和 error() 由编译器自动生成转发代码!
}
核心价值 :你只需要关注你想修改的方法,其他方法由编译器自动处理。
二、Kotlin 委托的本质:编译器在背后做了什么?
2.1 类委托的编译原理
当你写下:
kotlin
class OrderService(logger: Logger) : Logger by logger
Kotlin 编译器会生成等价于以下 Java 代码:
typescript
public final class OrderService implements Logger {
private final Logger logger;
public OrderService(Logger logger) {
this.logger = logger;
}
// 编译器自动生成的方法
public void info(String message) {
logger.info(message); // 自动转发
}
public void warn(String message) {
logger.warn(message); // 自动转发
}
public void error(String message) {
logger.error(message); // 自动转发
}
}
如果你 override 了某个方法:
kotlin
class OrderService(logger: Logger) : Logger by logger {
override fun info(message: String) {
println("[INFO] [${System.currentTimeMillis()}] $message")
}
}
编译器生成的代码会变成:
typescript
public final class OrderService implements Logger {
private final Logger logger;
public OrderService(Logger logger) {
this.logger = logger;
}
// 你 override 的方法,使用你的实现
public void info(String message) {
System.out.println("[INFO] [" + System.currentTimeMillis() + "] " + message);
}
// 你没有 override 的方法,编译器自动生成
public void warn(String message) {
logger.warn(message); // 自动生成
}
public void error(String message) {
logger.error(message); // 自动生成
}
}
2.2 属性委托的编译原理
当你写下:
csharp
class Example {
var name: String by NameDelegate()
}
编译器会生成:
arduino
public final class Example {
// 编译器创建的隐藏属性
private final NameDelegate name$delegate = new NameDelegate();
// 编译器生成的 getter
public String getName() {
return name$delegate.getValue(this,
new PropertyReferenceImpl(Example.class, String.class, "name", ...));
}
// 编译器生成的 setter
public void setName(String value) {
name$delegate.setValue(this,
new PropertyReferenceImpl(Example.class, String.class, "name", ...),
value);
}
}
关键点 :委托不是运行时动态代理,而是编译时代码生成,没有反射性能开销。
三、类委托:从入门到实战
3.1 基础语法
kotlin
// 1. 定义接口
interface Base {
fun print()
fun show()
}
// 2. 实现类
class BaseImpl(val x: Int) : Base {
override fun print() = println("打印: $x")
override fun show() = println("显示: $x")
}
// 3. 委托类
class Derived(b: Base) : Base by b
// 4. 使用
fun main() {
val base = BaseImpl(10)
val derived = Derived(base)
derived.print() // 输出: 打印: 10
derived.show() // 输出: 显示: 10
}
3.2 选择性覆盖
kotlin
class Derived(b: Base) : Base by b {
// 只覆盖 print 方法,show 方法自动委托
override fun print() {
println("Derived 的 print")
}
}
fun main() {
val base = BaseImpl(10)
val derived = Derived(base)
derived.print() // 输出: Derived 的 print
derived.show() // 输出: 显示: 10(自动委托)
}
3.3 实战:日志系统
kotlin
// 定义日志接口
interface Logger {
fun info(message: String)
fun warn(message: String)
fun error(message: String)
}
// 基础实现:控制台输出
class ConsoleLogger : Logger {
override fun info(message: String) = println("[INFO] $message")
override fun warn(message: String) = println("[WARN] $message")
override fun error(message: String) = println("[ERROR] $message")
}
// 装饰器:添加时间戳
class TimestampLogger(logger: Logger) : Logger by logger {
override fun info(message: String) {
logger.info("[${timestamp()}] $message")
}
override fun error(message: String) {
logger.error("[${timestamp()}] $message")
}
// warn() 自动委托,不加时间戳
private fun timestamp() = SimpleDateFormat("HH:mm:ss", Locale.CHINA).format(Date())
}
// 装饰器:添加文件输出
class FileLogger(logger: Logger) : Logger by logger {
override fun error(message: String) {
File("error.log").appendText("${Date()}: $message\n")
logger.error(message) // 仍然输出到控制台
}
// info() 和 warn() 自动委托
}
// 链式组合
fun main() {
val console = ConsoleLogger()
val timestamped = TimestampLogger(console)
val fullLogger = FileLogger(timestamped)
fullLogger.info("用户登录") // 带时间戳的 info
fullLogger.warn("磁盘空间不足") // 普通 warn(自动委托)
fullLogger.error("系统崩溃") // 同时输出到控制台和文件
}
输出结果:
css
[INFO] [15:42:39] 用户登录
[WARN] 磁盘空间不足
[ERROR] [15:42:39] 系统崩溃
关键点:通过链式委托,你可以像搭积木一样组合功能。
四、属性委托:从入门到实战
4.1 基础语法
kotlin
// 1. 定义委托类
class StringDelegate {
private var value: String = "默认值"
// 必须实现 getValue 操作符
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("读取属性: ${property.name}")
return value
}
// 必须实现 setValue 操作符(如果是 var 属性)
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
println("设置属性: ${property.name} = $newValue")
value = newValue
}
}
// 2. 使用委托
class Example {
var name: String by StringDelegate()
}
// 3. 测试
fun main() {
val example = Example()
example.name = "Kotlin" // 输出: 设置属性: name = Kotlin
println(example.name) // 输出: 读取属性: name \n Kotlin
}
4.2 标准库提供的委托
4.2.1 lazy:懒加载
kotlin
class HeavyService {
// 只在第一次访问时初始化
val config: AppConfig by lazy {
println("加载配置...")
Thread.sleep(2000) // 模拟耗时操作
AppConfig()
}
// 线程安全模式(默认)
val safeConfig: AppConfig by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
AppConfig()
}
// 非线程安全(性能更好)
val fastConfig: AppConfig by lazy(LazyThreadSafetyMode.NONE) {
AppConfig()
}
}
fun main() {
val service = HeavyService()
println("服务创建完成") // 此时 config 还未加载
service.config // 第一次访问,触发加载
service.config // 第二次访问,直接返回缓存
}
4.2.2 observable:属性变化监听
kotlin
class UserViewModel {
var userName: String by Delegates.observable("初始值") {
property, oldValue, newValue ->
println("${property.name}: '$oldValue' -> '$newValue'")
// 可以在这里更新 UI
}
var age: Int by Delegates.vetoable(0) {
property, oldValue, newValue ->
// 只有年龄在 0-150 之间才允许修改
newValue in 0..150
}
}
fun main() {
val vm = UserViewModel()
vm.userName = "张三" // 输出: userName: '初始值' -> '张三'
vm.userName = "李四" // 输出: userName: '张三' -> '李四'
vm.age = 25 // 成功
vm.age = 200 // 被拒绝,age 保持 25
println(vm.age) // 输出: 25
}
4.2.3 notNull:延迟非空赋值
kotlin
class DatabaseManager {
var connection: Connection by Delegates.notNull()
fun connect() {
connection = Connection("jdbc:mysql://localhost:3306/mydb")
}
fun query(sql: String) {
connection.execute(sql) // 如果未初始化,会抛出异常
}
}
4.2.4 Map 委托:从 Map 创建对象
kotlin
// 从 JSON 解析结果创建对象
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
val email: String by map
}
fun main() {
val json = mapOf(
"name" to "张三",
"age" to 25,
"email" to "zhangsan@example.com"
)
val user = User(json)
println(user.name) // 输出: 张三
println(user.age) // 输出: 25
}
五、实战:Android 开发中的委托
5.1 SharedPreferences 委托
kotlin
class PreferenceDelegate<T>(
private val context: Context,
private val name: String,
private val default: T
) {
private val prefs by lazy {
context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
}
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (default) {
is String -> prefs.getString(name, default as String) as T
is Int -> prefs.getInt(name, default as Int) as T
is Boolean -> prefs.getBoolean(name, default as Boolean) as T
is Float -> prefs.getFloat(name, default as Float) as T
is Long -> prefs.getLong(name, default as Long) as T
else -> throw IllegalArgumentException("不支持的类型")
}
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
is Long -> putLong(name, value)
}
apply()
}
}
}
// 使用
class SettingsManager(context: Context) {
var isDarkMode: Boolean by PreferenceDelegate(context, "dark_mode", false)
var username: String by PreferenceDelegate(context, "username", "")
var fontSize: Int by PreferenceDelegate(context, "font_size", 16)
}
5.2 ViewModel + SavedStateHandle 委托
kotlin
class UserViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
var userId: String by savedStateHandle
var userName: String by savedStateHandle
var isLoggedIn: Boolean by savedStateHandle
fun login(userId: String, userName: String) {
this.userId = userId
this.userName = userName
this.isLoggedIn = true
}
}
// 在 Activity 中使用
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 即使进程被杀,数据也会自动恢复
if (viewModel.isLoggedIn) {
showUserInfo(viewModel.userName)
}
}
}
六、委托 vs 装饰器 vs 代理:终极对比
| 维度 | 装饰器模式 | 代理模式 | Kotlin 委托 |
|---|---|---|---|
| 核心目的 | 动态添加功能 | 控制访问 | 消除样板代码 |
| 代码量 | 多(手动转发) | 多(手动转发) | 少(编译器生成) |
| 运行时开销 | 有(方法调用) | 有(方法调用) | 无(编译时生成) |
| 灵活性 | 高 | 高 | 高 |
| 学习成本 | 低 | 低 | 中(需要理解约定) |
七、最佳实践与常见陷阱
7.1 最佳实践
- 优先使用标准库委托 :
lazy、observable、vetoable等 - 委托类保持单一职责:一个委托只做一件事
- 注意线程安全 :多线程环境使用
LazyThreadSafetyMode.SYNCHRONIZED - 链式委托不要过深:超过 3 层建议重构
7.2 常见陷阱
kotlin
// 陷阱1:共享委托实例
val shared = ConsoleLogger()
class ServiceA : Logger by shared // 共享同一个实例
class ServiceB : Logger by shared // 可能导致状态冲突
// 正确:每个类使用独立实例
class ServiceA : Logger by ConsoleLogger()
class ServiceB : Logger by ConsoleLogger()
// 陷阱2:lazy 在并发环境下的问题
val heavy: HeavyObject by lazy(LazyThreadSafetyMode.NONE) {
HeavyObject() // 可能被多次创建
}
// 正确:使用线程安全的 lazy
val heavy: HeavyObject by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
HeavyObject()
}
八、总结
Kotlin 委托的核心价值
- 消除样板代码:编译器自动生成转发方法
- 灵活组合:可以链式委托,组合多个功能
- 松耦合:委托对象可以运行时替换
- 零运行时开销:编译时生成代码,没有反射
一句话记住
Kotlin 委托 = 装饰器模式 + 代理模式 - 样板代码
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!