Kotlin入门-委托(delegation)
委托定义
委托(Delegation) 是一种通过将职责交给另一个对象来实现代码复用的设计模式。它的本质就是对象的组合,将委托方对象传递给被委托方,让被委托方具备委托方的能力。
kotlin 委托通过 by 关键字实现,是 Kotlin 语言层面的特色功能。kotlin 支持2种委托方式:
- 类委托:实现继承的替换方案
- 属性委托:封装属性访问逻辑
// 类委托
class Derived(b: Base) : Base by b
// 属性委托
val name by lazy { "pp" }
委托原理
类委托
编译器会为委托类生成所有接口方法的转发调用,避免手动实现样板代码。
可以看看下面的例子:
//定义接口。
private interface Base {
fun printMessage()
fun printMessageLine()
}
//接口的实现类。负责打印Int属性成员变量
private class BaseImpl(private val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
//委托类。具备Base接口的能力
private class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
Base:定义接口,包含2个打印方法 BaseImpl:接口实现类。打印 Int 值 Derived:委托对象,虽然没有直接实现 Base 接口,因为委托也具备了Base 接口能力
语法解读:class Derived(b: Base) : Base by b 我们可以把这个代码拆成2部分解读:
- class Derived(b: Base) : Base 表示Derived 实现了 Base 接口
- by b 表示 Derived 不会显式实现具体的接口方法(当然也可以按需进行overrid实现重写对应的方法),具体的接口实现方法就使用 b 实例进行调用。
我们可以看看反编译后的 Derived 代码:
final class Derived implements Base {
// $FF: synthetic field
private final Base $$delegate_0;
public Derived(@NotNull Base b) {
Intrinsics.checkNotNullParameter(b, "b");
super();
this.$$delegate_0 = b;
}
public void printMessageLine() {
this.$$delegate_0.printMessageLine();
}
public void printMessage() {
String var1 = "abc";
System.out.print(var1);
}
}
可以看到:
- Derived 确实实现了Base 接口
- Derived 确实包含了Base 接口的方法实现,由于没有重写 printMessageLine 方法,所以该方法的实现是由 b 实例对应的printMessageLine 实现的。
- Derived 重写了 printMessage,打印字符串 “abc”, 所以该方法内部就没有使用 b 实例的引用了。
由此可以看出,通过 by 关键字可以节省很多模版代码(反编译后的接口方法都需要我们手动编写),这也是kotlin 的优势所在。
应用场景:实现装饰器模式。需要扩展功能,但不直接继承 Android 的 public ContextThemeWrapper(Context base, @StyleRes int themeResId) 就应用了委托的设计机制:在不改变context 功能的基础上,新扩展了指定具体主题的能力,一般可以应用于WindowManager 添加自定义View场景。
属性委托
属性委托通过 getValue() 和 setValue() 运算符重载实现:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Value from Delegate"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("Value set to $value")
}
}
应用场景
- layzy 懒加载
- 观察属性变化(Delegates.observable)
- 非空校验(Delegates.vetoable)
- SharedPreferences 封装
- ViewModel 属性委托(Android Jetpack)
layzy 懒加载
下面是layzy 关键字实现代码,从源码可以看出它确实是通过重写属性的get 方法来实现懒加载的。
LayzyJVM.kt 源码
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
注意:!== 是比较两个对象是否相等,即是否指向同一块内存地址(并非内容的比较)。
下面是一个懒加载的例子:定义一个name 属性,延迟加载,并在使用时调用2次,观察初始化日志(预期只执行一次),实际上”this is first call name“
class Delegate {
val name by lazy {
println("this is first call name")
"pp"
}
init {
val base = BaseImpl(10)
Derived(base).printMessage()
Derived(base).printMessageLine()
println("first call:$name")
println("second call:$name")
}
}
执行测试用例代码会输出下面内容:
abc10
this is first call name
first call:pp
second call:pp
观察属性变化(Delegates.observable)
class Delegate {
val name by lazy {
println("this is first call name")
"pp"
}
var title: String by Delegates.observable("初始化pp") { prop, old, new ->
println("oldValue:$old, newValue:$new")
}
init {
val base = BaseImpl(10)
Derived(base).printMessage()
Derived(base).printMessageLine()
println("first call:$name")
println("second call:$name")
title = "new title pp"
}
}
执行测试用例代码会输出下面内容:
abc10
this is first call name
first call:pp
second call:pp
oldValue:初始化pp, newValue:new title pp
非空校验(Delegates.vetoable)
Delegates.vetoable 和 Delegates.observable 的区别在于调用顺序的差异。
- Delegates.observable: 在变量赋值之后回调
- Delegates.vetoable:在变量赋值之前回调,并且可以新增拦截逻辑,拦截方法返回true表示可以赋值,返回false则不能赋值
可以继续看下面 Delegates.vetoable 的例子 这次我们新增了一个成员变量 notNullCheck 字符串,并在初始化时分别赋值 “”,null,”final not null”. 由于添加
class Delegate {
val name by lazy {
println("this is first call name")
"pp"
}
var title: String by Delegates.observable("初始化pp") { prop, old, new ->
println("oldValue:$old, newValue:$new")
}
var notNullCheck: String? by Delegates.vetoable("参数不为空") {
prop, old, new ->
if (new.isNullOrEmpty()) {
println("new value is null, so not set new value")
return@vetoable false
} else {
println("new value is not null,so set new value. oldValue:$old, newValue:$new")
return@vetoable true
}
}
init {
val base = BaseImpl(10)
Derived(base).printMessage()
Derived(base).printMessageLine()
println("first call:$name")
println("second call:$name")
title = "new title pp"
notNullCheck = ""
println("notNullCheck first call:$notNullCheck")
notNullCheck = null
println("notNullCheck second call:$notNullCheck")
notNullCheck = "final not null"
println("notNullCheck third call:$notNullCheck")
}
}
再执行用例,发现会打印下面日志,基本符合预期:
abc10
this is first call name
first call:pp
second call:pp
oldValue:初始化pp, newValue:new title pp
notNullCheck first call:参数不为空
new value is null, so not set new value
notNullCheck second call:参数不为空
new value is not null,so set new value. oldValue:参数不为空, newValue:final not null
notNullCheck third call:final not null
SharedPreferences 封装
下面是一个 SharedPreferences 委托,即变量的获取和设置 都是通过sp操作的,但是这个操作对于业务来说是透明的,已经在变量定义的时候通过 委托 指定了
class PreferenceDelegate<T>(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: T
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> prefs.getString(key, defaultValue) as T
is Int -> prefs.getInt(key, defaultValue) as T
is Boolean -> prefs.getBoolean(key, defaultValue) as T
else -> throw IllegalArgumentException("Unsupported type")
}
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
else -> throw IllegalArgumentException("Unsupported type")
}.apply()
}
}
}
// 使用
var userId by PreferenceDelegate(prefs, "user_id", 0)
由于测试用例中不太好mock sp操作,这里大家感兴趣可以在Android项目中自行验证。
委托 VS 继承
| 维度 | 委托 | 继承 |
|---|---|---|
| 耦合度 | 低(对象组合) | 高(类继承) |
| 灵活性 | 可运行时切换受托对象 | 编译时固定 |
| 多态支持 | 需手动转发方法 | 自动支持 |
| 适用场景 | 需要复用实现但不想耦合父类 | 明确的 “is-a” 关系 |
总结
Kotlin 委托的核心价值:
- 减少样板代码:自动生成委托方法(类委托)
- 关注点分离:将属性管理逻辑抽离(属性委托)
- 灵活扩展:通过组合而非继承增强功能
选择时机:
- 当需要 复用实现但避免继承 时 → 类委托
- 当需要 统一管理属性行为 时 → 属性委托
通过委托,Kotlin 以简洁的语法实现了强大的设计模式,是提升代码质量的利器。
参考文档
