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 监听变化并启动协程处理

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

相关推荐
啃火龙果的兔子8 小时前
Kotlin 修改安装到桌面后的应用图标
开发语言·kotlin·harmonyos
来来走走8 小时前
Android开发(Kotlin) ViewModel基本用法
android·开发语言·kotlin
用户69371750013849 小时前
6.Kotlin 流程控制:循环控制:while 与 do/while
android·后端·kotlin
Entropless11 小时前
Kotlin 可以预判你的预判?Kotlin 高级特性 之 Contracts
android·kotlin
用户65783000349215 小时前
kotlin 中 return@key 用法
kotlin
来来走走20 小时前
Android开发(Kotlin) 协程
android·java·kotlin
邮专薛之谦1 天前
Kotlin 全知识点复习+详细梳理
windows·kotlin·android studio·idea
基哥的奋斗历程1 天前
Kotlin_Flow_完整使用指南
android·开发语言·kotlin
用户69371750013841 天前
4.Kotlin 流程控制:强大的 when 表达式:取代 Switch
android·后端·kotlin
用户69371750013841 天前
5.Kotlin 流程控制:循环的艺术:for 循环与区间 (Range)
android·后端·kotlin