目录
-
什么是委托?
-
我们先来看看委托的基础语法
2.1 类委托:让一个类"代理"另一个类的功能
2.2 属性委托:延迟初始化
-
总结一下委托的好处
-
那么在代码中如何使用?应用场景。
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 方法,从而实现属性的延迟初始化逻辑。
三、总结一下委托的好处
- 避免重复写代码:通过上面的代码,我们也知道,多个类需要相同功能时,将类传递进来,交给委托对象统一处理。 可以省掉了很多代码。
- 代替继承:继承是"父子关系",不够灵活;委托是"合作关系",更灵活。
- 解耦代码:把功能拆分到不同的委托对象中,让代码更干净。
- 动态切换功能:运行时可以换不同的委托对象,实现不同行为。
- 结合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 表示:
- 不保证线程安全,初始化代码可能在多线程环境下 被多次执行。
- 性能更高,因为跳过了同步锁检查。
那么这里为什么敢使用NONE呢?
因为mBinding 的初始化必然发生在主线程:BaseFrameDialog 的构造函数在创建对话框时调用,而对话框的实例化通常发生在主线程(例如在 Activity 的 onCreate 中)。因此,createVB() 方法生成 ViewBinding 的代码也必然在主线程执行。
由于所有对 mBinding 的访问都被限制在主线程,不存在多线程竞争初始化的问题,因此无需线程安全机制。