Kotlin之类委托和属性委托

在 Kotlin 中,委托(Delegation) 是一种设计模式,核心思想是 "将一个类 / 属性的部分职责委托给另一个对象",通过 by 关键字实现。协程(Coroutines)本身与委托没有直接关联,但委托模式可以与协程结合使用(例如在委托逻辑中调用 suspend 函数)。下面详细讲解 Kotlin 中的类委托属性委托,以及它们与协程的结合场景。

一、类委托(Class Delegation)

类委托通过 by 关键字将接口的实现委托给另一个对象,避免手动实现接口的所有方法,减少代码冗余。

基本语法

kotlin

复制代码
interface Base {
    fun doSomething()
    fun doAnotherThing()
}

class BaseImpl : Base {
    override fun doSomething() {
        println("BaseImpl doSomething")
    }
    override fun doAnotherThing() {
        println("BaseImpl doAnotherThing")
    }
}

// 类 Derived 将 Base 接口的实现委托给 baseImpl 对象
class Derived(base: Base) : Base by base {
    // 可以重写委托对象的方法(可选)
    override fun doSomething() {
        println("Derived doSomething (before delegation)")
        // 调用委托对象的原方法(可选)
        (this as Base).doSomething() // 或直接使用 base.doSomething()
    }
}

fun main() {
    val base = BaseImpl()
    val derived = Derived(base)
    derived.doSomething() // 输出:Derived doSomething (before delegation) → BaseImpl doSomething
    derived.doAnotherThing() // 输出:BaseImpl doAnotherThing(直接使用委托实现)
}
与协程结合

如果接口包含 suspend 函数(需在协程中调用),类委托同样适用:

kotlin

复制代码
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

interface CoroutineTask {
    suspend fun execute()
}

class TaskImpl : CoroutineTask {
    override suspend fun execute() {
        delay(1000) // 模拟耗时操作(协程暂停)
        println("TaskImpl executed")
    }
}

class TaskDelegator(task: CoroutineTask) : CoroutineTask by task {
    // 重写 suspend 函数
    override suspend fun execute() {
        println("TaskDelegator before execution")
        super.execute() // 调用委托对象的 suspend 函数
        println("TaskDelegator after execution")
    }
}

fun main() = runBlocking {
    val task = TaskImpl()
    val delegator = TaskDelegator(task)
    delegator.execute() 
    // 输出:
    // TaskDelegator before execution
    // (等待 1 秒)
    // TaskImpl executed
    // TaskDelegator after execution
}

二、属性委托(Property Delegation)

属性委托通过 by 关键字将属性的访问逻辑(getter/setter) 委托给另一个对象,常用场景包括延迟初始化、属性变化监听、存储管理等。

委托类需实现以下接口(根据属性是否可变):

  • 对于只读属性(val) :委托类需实现 ReadOnlyProperty<thisRef, T> 接口,提供 getValue(thisRef, property) 方法。
  • 对于可变属性(var) :委托类需实现 ReadWriteProperty<thisRef, T> 接口,提供 getValue(...)setValue(...) 方法。
1. 延迟初始化(lazy 委托)

lazy 是 Kotlin 标准库提供的属性委托,用于延迟初始化属性(首次访问时才初始化,且仅初始化一次),适合初始化成本高的对象(如网络连接、数据库实例)。

基本用法

kotlin

复制代码
val expensiveResource: String by lazy {
    println("Initializing...") // 首次访问时执行
    "Resource data" // 初始化结果
}

fun main() {
    println("Before access")
    println(expensiveResource) // 首次访问:触发初始化
    println(expensiveResource) // 第二次访问:直接使用缓存值
    // 输出:
    // Before access
    // Initializing...
    // Resource data
    // Resource data
}
与协程结合

lazy 默认是线程安全的 (多线程下仅初始化一次),但在协程中需注意:lazy 的初始化逻辑默认运行在触发访问的线程 / 协程中,若初始化逻辑包含 suspend 函数,需结合 runBlocking 或自定义协程安全的延迟初始化(lazy 本身不支持 suspend 初始化逻辑)。

kotlin

复制代码
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

// 错误示例:lazy 初始化块中不能直接调用 suspend 函数
// val coroutineResource: String by lazy {
//     delay(1000) // 编译错误:suspend 函数不能在非 suspend 环境调用
//     "Data"
// }

// 正确示例:用 runBlocking 包装(适合简单场景,会阻塞当前线程)
val coroutineResource: String by lazy {
    runBlocking {
        delay(1000) // 协程暂停(但 runBlocking 会阻塞当前线程)
        "Coroutine resource"
    }
}

fun main() = runBlocking {
    println("Before access")
    println(coroutineResource) // 首次访问:阻塞 1 秒后输出
    println(coroutineResource) // 直接使用缓存值
}

注意:runBlocking 会阻塞线程,若需非阻塞的协程延迟初始化,可自定义委托(见下文 "自定义属性委托")。

2. 监听属性变化(observable 委托)

observable 是标准库提供的委托(位于 kotlin.properties),用于监听属性值的变化(初始化和修改时触发回调)。

基本用法

kotlin

复制代码
import kotlin.properties.Delegates

class User {
    // 监听 name 属性的变化
    var name: String by Delegates.observable("<initial>") { property, oldValue, newValue ->
        println("${property.name} changed: $oldValue → $newValue")
    }
}

fun main() {
    val user = User()
    user.name = "Alice" // 输出:name changed: <initial> → Alice
    user.name = "Bob"   // 输出:name changed: Alice → Bob
}
与协程结合

在监听回调中可以调用协程相关逻辑(如通过 CoroutineScope 启动协程处理变化):

kotlin

复制代码
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.properties.Delegates

class DataHolder(scope: CoroutineScope) {
    // 监听 data 属性变化,并在 IO 线程处理
    var data: String by Delegates.observable("") { _, old, new ->
        scope.launch(Dispatchers.IO) {
            println("Processing change on ${Thread.currentThread().name}: $old → $new")
            delay(500) // 模拟异步处理(非阻塞)
            println("Processed new value: $new")
        }
    }
}

fun main() = runBlocking {
    val dataHolder = DataHolder(this)
    dataHolder.data = "First update"
    dataHolder.data = "Second update"
    delay(1000) // 等待协程处理完成
    // 输出(线程名可能不同):
    // Processing change on DefaultDispatcher-worker-1:  → First update
    // Processing change on DefaultDispatcher-worker-2: First update → Second update
    // Processed new value: First update
    // Processed new value: Second update
}
3. 自定义属性委托

除了标准库提供的委托,还可以自定义委托类,实现更灵活的逻辑(如协程安全的延迟初始化、数据库存储委托等)。

示例:协程安全的延迟初始化委托

实现一个支持 suspend 初始化逻辑的延迟委托(非阻塞):

kotlin

复制代码
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.reflect.KProperty

// 自定义只读属性委托:支持 suspend 初始化
class CoroutineLazy<T>(
    private val initializer: suspend () -> T
) {
    private var value: T? = null
    private val mutex = Mutex() // 协程安全锁(避免并发初始化)

    suspend operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value == null) {
            mutex.withLock { // 确保初始化逻辑仅执行一次
                if (value == null) {
                    value = initializer()
                }
            }
        }
        return value!!
    }
}

// 便捷函数:创建 CoroutineLazy 实例
fun <T> coroutineLazy(initializer: suspend () -> T) = CoroutineLazy(initializer)

// 使用示例
class Repository {
    // 用自定义委托延迟初始化(支持 suspend 逻辑)
    val remoteData: String by coroutineLazy {
        println("Fetching data from network...")
        delay(1000) // 模拟网络请求(非阻塞)
        "Remote data"
    }
}

fun main() = runBlocking {
    val repo = Repository()
    println("Before fetch")
    println(repo.remoteData) // 首次访问:触发 suspend 初始化
    println(repo.remoteData) // 直接使用缓存值
    // 输出:
    // Before fetch
    // Fetching data from network...
    // (等待 1 秒)
    // Remote data
    // Remote data
}

三、委托与协程的结合场景总结

  1. 类委托:将接口中包含 suspend 函数的实现委托给其他类,简化代码复用(如代理网络请求、异步任务)。
  2. lazy 委托:延迟初始化协程中需要的资源(注意线程安全,复杂场景需自定义协程安全委托)。
  3. observable 委托 :监听属性变化时,通过 CoroutineScope 启动协程处理异步逻辑(如网络请求、数据库更新)。
  4. 自定义委托:实现协程特有的逻辑(如非阻塞延迟初始化、协程安全的属性访问控制)。

四、核心区别

委托类型 核心作用 与协程结合点
类委托 接口实现的复用 委托包含 suspend 函数的接口
属性委托 简化属性的 getter/setter 逻辑 lazy 延迟初始化资源、observable 监听变化并启动协程处理

通过委托模式,可在协程中更优雅地实现代码复用和逻辑解耦,尤其适合处理异步场景下的属性管理和接口实现。

相关推荐
I'm Jie6 小时前
Gradle 的项目结构与源码集(Source Sets)详解(Kotlin DSL)
android·java·开发语言·spring boot·spring·kotlin·gradle
Android-Flutter6 小时前
kotlin - 显示heic图片
android·kotlin
2501_938780286 小时前
Kotlin Multiplatform Mobile(KMM):实现 iOS 与 Android 共享业务逻辑
android·ios·kotlin
消失的旧时光-19438 小时前
摇杆控制View
android·kotlin
明道源码12 小时前
Kotlin Multiplatform 跨平台方案解析以及热门框架对比
开发语言·kotlin·cocoa
消失的旧时光-194318 小时前
Kotlin 协程最佳实践:用 CoroutineScope + SupervisorJob 替代 Timer,实现优雅周期任务调度
android·开发语言·kotlin
错把套路当深情18 小时前
Kotlin保留小数位的三种方法
开发语言·python·kotlin
错把套路当深情18 小时前
Kotlin基础类型扩展函数使用指南
python·微信·kotlin
Frank_HarmonyOS18 小时前
Kotlin 协程之launch、async、suspend 函数和调度器(Dispatchers)
kotlin