Kotlin的委托是什么?在看源码的时候不知道他的作用是什么,为什么使用,那么你看看这篇文章。

目录

  1. 什么是委托?

  2. 我们先来看看委托的基础语法

    2.1 类委托:让一个类"代理"另一个类的功能

    2.2 属性委托:延迟初始化

  3. 总结一下委托的好处

  4. 那么在代码中如何使用?应用场景。

    4.1 LazyThreadSafetyMode.NONE


一、什么是委托?

想象你要出国旅游,但不会说英语,于是你找了个翻译帮你沟通。翻译就是你的"委托对象",你"委托"翻译帮你处理语言问题。

在 Kotlin 中,委托(Delegation) 就是这个道理:让一个对象(委托对象)代替另一个对象(被委托对象)完成某些任务。

用代码来举例,就是:

kotlin 复制代码
// 定义接口
interface Player {
    fun play()
    fun stop()
}

// 普通播放器实现接口
class NormalPlayer : Player {
    override fun play() { println("播放音乐") }
    override fun stop() { println("停止播放") }
}

// 增强播放器,通过委托复用 NormalPlayer 的代码
class EnhancedPlayer(private val player: Player) : Player by player {
    // 只重写需要增强的方法
    override fun play() {
        println("增强音效!")
        player.play() // 调用委托对象的 play()
    }
}

fun main() {
    val normalPlayer = NormalPlayer()
    val enhancedPlayer = EnhancedPlayer(normalPlayer)
    
    enhancedPlayer.play() // 输出:增强音效! → 播放音乐
    enhancedPlayer.stop() // 输出:停止播放(直接调用委托对象的方法)
}

EnhancedPlayer 通过 by player 将接口方法委托给 player 对象。 只重写需要增强的方法(如 play()),其他方法(如 stop())自动调用委托对象的方法

如果学过装饰者设计模式,会发现有一举同工之妙。


二、我们先来看看委托的基础语法

2.1 类委托:让一个类"代理"另一个类的功能

其实就是上面的播放器代码,下面代码就省略掉。 场景:比如你有一个播放器接口,想让一个普通播放器和增强播放器都实现这个接口,但不想重复写代码。

2.2 属性委托:

场景:比如你想实现一个属性的延迟加载(第一次访问时才初始化)。

kotlin 复制代码
// 延迟初始化:第一次访问时才计算值
    val bio: String by lazy {
        println("计算 bio...")
        "我是程序员,喜欢 Kotlin!"
    }

fun main() {
    val user = User()
    
    // 1. 访问 bio(第一次触发计算)
    println(user.bio) // 输出:计算 bio... → 我是程序员,喜欢 Kotlin!
    println(user.bio) // 直接输出:我是程序员...(不再计算)
}

lazy:用于延迟初始化,常用于资源密集型操作(如读取文件、数据库连接)。

属性委托,委托给谁呢?属性委托实际上是将属性的 ​getter 和 ​setter 的访问逻辑委托给另一个对象​(即委托对象)

这里 lazy { ... } 返回的是一个 Lazy 对象,它内部实现了 getValue 方法。当访问 myValue 时,实际会调用 Lazy 对象的 getValue 方法,从而实现属性的延迟初始化逻辑


三、总结一下委托的好处

  1. 避免重复写代码:通过上面的代码,我们也知道,多个类需要相同功能时,将类传递进来,交给委托对象统一处理。 可以省掉了很多代码。
  2. 代替继承:继承是"父子关系",不够灵活;委托是"合作关系",更灵活。
  3. 解耦代码:把功能拆分到不同的委托对象中,让代码更干净。
  4. 动态切换功能:运行时可以换不同的委托对象,实现不同行为。
  5. 结合lazy实现延迟初始化:让资源得到更好、安全的利用。

四、那么在代码中如何使用?应用场景

我们在看源码的时候,会发现有很多使用了委托的地方。

kotlin 复制代码
private val mToastHandler by lazy { 
    Handler(Looper.getMainLooper()) 
}

这里的意思就是,我使用mToastHandler的时候,才会赋值 Handler(Looper.getMainLooper()) 过来 。

但我们在来看看这个。

kotlin 复制代码
//父类
abstract class BaseFrameDialog<VB : ViewBinding> (
    protected var mContext: Context,
) :
    Dialog(mContext, R.style.base_DialogBgD),FrameView<VB> {

    protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { createVB() }
}

//子类实现

/**
 * 开关机的对话框
 */
class DevOnOffDialog(mContext: Context) : BaseFrameDialog<BackstageDialogDevonoffBinding>(mContext) { 
    override fun BackstageDialogDevonoffBinding.initView() {
        tvBack.setOnClickListener {
            dismiss()
        }
    }
    override fun createVB() = BackstageDialogDevonoffBinding.inflate(layoutInflater)
}

protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { createVB() }为什么mBinding需要延迟初始化呢?一般这个属性不是一定会使用的?还需要延迟初始化节省资源是为了什么??

其实延迟初始化,不仅仅是为节省资源,还有一个是因为生命周期、时间顺序的限制。

在 Android 的 Activity 或 Fragment 中,视图绑定(View Binding)或数据绑定(Data Binding)的初始化必须在 生命周期回调之后(例如 onCreate() 或 onCreateView()),否则会引发崩溃。所以,我们需要等到他用的时候才初始化,这样之前肯定就准备好了


4.1 我们会注意有一个mode = LazyThreadSafetyMode.NONE

LazyThreadSafetyMode.NONE 表示:

  1. 不保证线程安全,初始化代码可能在多线程环境下 被多次执行。
  2. 性能更高,因为跳过了同步锁检查。

那么这里为什么敢使用NONE呢?

因为mBinding 的初始化必然发生在主线程:BaseFrameDialog 的构造函数在创建对话框时调用,而对话框的实例化通常发生在主线程(例如在 Activity 的 onCreate 中)。因此,createVB() 方法生成 ViewBinding 的代码也必然在主线程执行。

由于所有对 mBinding 的访问都被限制在主线程,​不存在多线程竞争初始化的问题,因此无需线程安全机制。

相关推荐
tpoog30 分钟前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
louisgeek32 分钟前
Android View TouchDelegate
android
sukida1001 小时前
Firefox 浏览器同步一个账户和书签网址
android·linux·firefox
每次的天空1 小时前
Android 单例模式全解析:从基础实现到最佳实践
android·单例模式
Tsing7222 小时前
Android窗口Surface简介
android
诺亚凹凸曼2 小时前
23种设计模式-结构型模式-桥接器
android·java·设计模式
louisgeek2 小时前
Kotlin Flow 操作符 map 和 flatMap 的区别
kotlin
帅次3 小时前
Flutter DropdownButton 详解
android·flutter·ios·kotlin·gradle·webview
际宇人3 小时前
移动端APP阿里云验证码2.0接入实录
android
.又是新的一天.3 小时前
02_MySQL安装及配置
android·数据库·mysql