Kotlin 类委托与属性委托
Kotlin 委托本质—委托模式
在Kotlin中,可以用过by
关键字很好的实现委托,而Kotlin中委托其实就是委托模式的实现,而委托模式已经被证明是实现继承的好方式,通过by
关键字将接口的实现委托给另一个对象。这样代码会更简洁,符合Kotlin的惯用写法。首先先介绍一下委托模式。
委托模式的核心是让一个对象将某些职责交给另一个对象处理,举个例子,你要维护一个列表,在某些情况下可能是查询、也有可能是批量添加或者删除,也有可能是排序,在Java中,你不知道具体的使用ArrayList
还是LinkedList
来实现,亦或是两个都实现一遍,这个时候就可以使用委托模式,使用委托对象,谁需要使用根据具体的情况选择ArrayList
还是LinkedList
。再比如Android中的Context
,在Context
类中就有很多未实现的方法,他只是实现了一个标准,而ContextWrapper
持有了一个Context
对象,具体的方法实现实在具体的对象,也就是Context
的子类。
委托模式
委托模式拥有固定的模板,需要定义委托接口(行为),委托对象,委托者,委托着通过委托对象去实现某个行为(委托接口),我们在Java中实现一下委托模式:
首先定义一下委托行为,即接口也可以是抽象类,主要是用来指定规范
/**
* 定义委托接口行为
*/
public interface ParcelKeeper {
void keep(String parcelId);
}
/**
* 第一种实现方式
*/
public class ParcelLocker implements ParcelKeeper {
@Override
public void keep(String parcelId) {
System.out.println(parcelId);
}
}
/**
* 第二种实现方式
*/
public class ParcelStation implements ParcelKeeper {
@Override
public void keep(String parcelId) {
System.out.println(parcelId);
}
}
/**
* 委托者
*/
public class Courier implements ParcelKeeper {
// 关键点:持有委托对象
private final ParcelKeeper keeper;
public Courier(ParcelKeeper keeper) {
this.keeper = keeper;
}
@Override
public void keep(String parcelId) {
//使用委托对象的方法
keeper.keep(parcelId);
}
public void save(String parcelId) {
keep(parcelId);
}
}
委托者不关心具体实现细节,由具体接口的实现类对象去处理相关的行为。
Kotlin的委托实现
我们按照委托模式的思想来通过Kotlin实现:首先还是定义一个接口
/**
* 定义委托协议(需要做什么)
*/
interface IBaseDoing {
val name:String
fun doSomething()
fun printMessage(msg: String)
}
/**
* 具体的实现类1
*/
class ImplOne : IBaseDoing {
override val name = "ImplOne"
override fun doSomething() {
println("ImplOne doSomething")
}
override fun printMessage(msg: String) {
println("$name : $msg")
}
}
/**
* 具体的实现类2
*/
class ImplTow : IBaseDoing {
override val name = "ImplOne"
override fun doSomething() {
println("ImplTow doSomething")
}
override fun printMessage(msg: String) {
println("$name : $msg")
}
}
Kotlin中委托的语法是:class 类A(val obj: 接口B) : 接口B by obj { }
/**
* 委托类,将自己的具体实现委托给IBaseDoing的实现类
*/
class Custom(val obj: IBaseDoing) : IBaseDoing by obj {
override val name: String
get() = "Custom"
override fun doSomething() {
println("this is Custom class override: $name")
}
}
fun main() {
val custom1 = Custom(obj = ImplOne())
custom1.doSomething()
val custom2 = Custom(obj = ImplTow())
custom2.printMessage("hello kotlin")
}
测试结果:
在Kotlin中接口定义属性与方法,我们都可以在委托对象中直接重写属性的值和方法,值得注意的是,委托对象重写的属性,原有接口实现类中无法访问,例如上述中的类ImplTow
中的name
,其中printMessage
方法是无法访问委托对象覆盖的属性。 同时我们也可以委托多个接口,有选择的重写其中的方法:
interface A {
fun one()
}
interface B {
fun two()
}
class DelegateAB(private val a: A, private val b: B) : A by a, B by b {
override fun one() {
}
override fun two() {
}
}
Kotlin的属性委托
属性委托基础
Kotlin中不仅有接口委托,还有属性委托,属性委托的语法是:val/var <属性名>: <类型> by <表达式>
,在 Kotlin 中,属性委托(Property Delegation)允许你将属性的 读(get) 和 写(set) 操作委托给另一个对象,从而复用或扩展属性的行为。这种机制通过 by 关键字实现,是 Kotlin 实现代码复用和逻辑解耦的重要特性。下面举个简单的例子实现属性委托:
class Cat {
var name: String by MyDelegate("Kitty")
}
class MyDelegate(private var initValue: String) {
operator fun getValue(cat: Cat?, property: KProperty<*>): String {
return initValue
}
operator fun setValue(cat: Cat?, property: KProperty<*>, value: String) {
//这里不要直接赋值:cat?.name = value,会无限递归,变为java字节码反编译后可看调用原理
initValue = value
}
}
Kotlin 标准库中的内置属性委托
-
延迟属性 Lazy properties
val myLazyValue: String by lazy { println("Initializing...") "Hello, World!" } } val age: Int by lazy(LazyThreadSafetyMode.PUBLICATION) { 23 }
值得一提的是,Kotlin的
by lazy
又三种模式,SYNCHRONIZED
、PUBLICATION
、NONE
,他们的区别在于是否是线程安全的,实现安全的方式,加锁或者CAS,可以看看by lazy
的源码实现。 -
observable:监听属性变化
val observableProp: String by Delegates.observable("value1") { property: KProperty<*>, oldValue: String, newValue: String -> println("oldValue:$oldValue newValue:$newValue") }
-
vetoable:条件拦截属性赋值,,符合lambda表达式的条件才会改变属性的值
var con: Int by Delegates.vetoable(0) { _, oldValue, newVal -> newVal > oldValue }
核心代码是ObservableProperty类中的setValue方法中进行了拦截
-
Map 委托:将属性映射到 Map
class User(private val map: Map<String, Any>) { private val name by map private val age by map private val isSuper: Boolean by map override fun toString(): String { return "[name:$name,age:$age,isSuper:$isSuper]" } } fun main() { val user = User(mapOf("age" to 32, "isSuper" to false, "name" to "Tom")) println(user.toString()) }
通过map可以为属性赋值。
属性委托原理
其实属性委托的主要原理就是编译器为我们编译相关代码实现getValue和setValue方法的调用,官网文档也给出了解释:
Kotlin相关的委托基本上就介绍完毕了,学习这些主要是为后续的Android Compose打个基础,Compose库中使用了大量的Kotlin 高阶函数、Lambda表达式、委托等相关特性,因此这些基础就是更好的看懂Compose的源码,特此巩固下这块的知识。