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

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

相关推荐
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z2 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton3 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream3 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam3 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker4 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc4 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景4 天前
kotlin协程学习小计
android·kotlin
Kapaseker5 天前
你搞得懂这 15 个 Android 架构问题吗
android·kotlin
zh_xuan5 天前
kotlin 高阶函数用法
开发语言·kotlin