一、关于委托
委托在实际开发中一直不受重视,这是因为委托的应用场景不清晰,但Kotlin 的委托在软件架构中可以发挥巨大的作用,例如Jetpack Compose中就大量使用了委托。
Kotlin的委托主要是二个应用场景,一个是委托类,另一个是委托属性,接下来一个个分析。
二、委托类
定义一个Animal接口,并增加一个需要实现的方法eat,如下:
interface Animal {
fun eat()
}
然后增加二个实现类,兔子和老虎,如下:
class Rabbit : Animal {
override fun eat() {
print("吃草")
}
}
class Tiger : Animal {
override fun eat() {
print("吃肉")
}
}
假如现在要增加狮子,然后狮子也吃肉,跟老虎的eat一模一样,那假如我们增加一个狮子类,如下:
class Lion:Animal {
override fun eat() {
print("吃肉")
}
}
就会发现除了类名不一样,狮子Lion的eat行为和老虎Tiger的eat行为一样,我们多写了很多模板代码,于是我们就可以将狮子Lion的eat行为委托给老虎Tiger。在kotlin中用by关键字就可以实现委托,如下:
class Lion(tiger: Tiger) : Animal by tiger
//使用
Lion(Tiger()).eat()
可以看到虽然我们实现了Animal接口,但是我们并没有写任何实现,这就是委托,通过by关键字将实现全部委托给了传入的tiger:Tiger。如果将传入的对象改为接口animal:Animal而不是接口的子类则更具扩展性(通用):
class Lion(animal: Animal) : Animal by animal
所以说,Kotlin 的委托类提供了语法层面的委托模式。通过这个 by 关键字,就可以自动将接口里的方法委托给一个对象,从而可以帮我们省略很多接口方法适配的模板代码。
三、委托属性
Kotlin“委托类”委托的是接口方法,而“委托属性”委托的,则是属性的getter、setter。属性的 getter、setter 委托出去以后,能有什么用呢?我们可以从 Kotlin 官方提供的标准委托那里找到答案。
1、标准委托
Kotlin 提供了好几种标准委托,其中包括两个属性之间的直接委托、by lazy 懒加载委托、Delegates.observable 观察者委托,以及 by map 映射委托。前面两个的使用频率比较高,后面两个频率比较低。这里,我们就主要来了解下前两种委托属性。
①将属性 A 委托给属性 B
从 Kotlin 1.4 开始,我们可以直接在语法层面将“属性 A”委托给“属性 B”,就像下面这样:
class Person {
var age: Int = 18
//将newAge的getter、setter委托给age
//::age是属性的引用
var newAge: Int by ::age
}
age 和 newAge 两者之间的委托关系一旦建立,就代表了它们两者的 getter 和 setter 会完全绑定在一起,如果要用代码来解释它们背后的逻辑,它们之间的关系会是这样:
class Person {
var age: Int = 18
var newAge: Int
get() = age
set(value) {
age = value
}
}
这种委托的应用场景是可以做到软件版本的兼容,比如2.0版本之前用的age字段,而2.0版本之后用newAge字段,我们可以将newAge委托给age就能保证不同的软件版本取到的不管是age还是newAge取到的值是一致的。
②懒加载委托
被访问的时候才去触发,从而避免不必要的资源开销。例如创建集合:
val list: MutableList<Int> by lazy { mutableListOf() }
懒加载委托的源代码,其实是一个高阶函数:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
可以看到,lazy() 函数可以接收一个LazyThreadSafetyMode 类型的参数,如果我们不传这个参数,它就会直接使用SynchronizedLazyImpl的方式。而且通过它的名字我们也能猜出来,它是为了多线程同步的。而剩下的 SafePublicationLazyImpl、UnsafeLazyImpl,则不是多线程安全的。
③Delegates.observable 观察者委托
看如下的代码:
class Person {
var age: Int by Delegates.observable(0) { property, oldValue, newValue -> print("observable:oldValue:$oldValue,newValue:$newValue") }
}
0为初始值,调用代码修改这个初始值:
val person = Person()
person.age = 15
person.age = 18
可以发现打印了二条print,这个属性的代理比较简单,当属性改变时会发出通知。除了这个还有另外一个,当返回值为true时允许修改属性值,为false则返回原值,如下:
class Person {
var age: Int by Delegates.vetoable(0) { property, oldValue, newValue -> getResult(property,oldValue,newValue) }
private fun getResult(property: KProperty<*>, oldValue: Int, newValue: Int): Boolean {
Log.d("", "vetoable:oldValue:$oldValue,newValue:$newValue")
return true
}
}
④by map 映射委托
在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。看如下的代码:
class Person(var map: MutableMap<String, Any?>) {
var userName: String by map
var age: Int by map
var nickName: String by map
var isAult: Boolean by map
}
调用,委托属性会从这个映射中取值(通过字符串键——属性的名称):
val person = Person(mutableMapOf("userName" to "张三", "age" to 18,"nickName" to "大牛","isAult" to false))
Log.d("TAG", "person:${person.userName},${person.age},${person.nickName},${person.isAult}") //person:张三,18,大牛,false
2、自定义委托属性
①自定义实现
自定义委托,必须遵循 Kotlin 制定的规则。如下代码:
class StringDelegate(private var oldValue: String = "Hello") {
operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
return oldValue
}
operator fun setValue(thisRef: Owner, property: KProperty<*>, newValue: String) {
oldValue = newValue
}
}
class Owner {
var text: String by StringDelegate("World")
init {
Log.d("TAG","text:$text")
}
}
1)、两个方法必须有 operator 关键字修饰
2)、text 属性是处于 Owner 这个类当中的,因此getValue、setValue 这两个方法中的 thisRef 的类型,必须要是 Owner,或者是 Owner 的父类。
3)、text 属性是 String 类型的,为了实现对它的委托,getValue 的返回值类型,以及 setValue 的参数类型,都必须是 String 类型或者是它的父类。
②ReadWriteProperty、ReadOnlyProperty
借助 Kotlin 提供的 ReadWriteProperty、ReadOnlyProperty 这两个接口,来自定义委托。源码如下:
package kotlin.properties
import kotlin.reflect.KProperty
/**
* Base interface that can be used for implementing property delegates of read-only properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param T the type of object which owns the delegated property.
* @param V the type of the property value.
*/
public fun interface ReadOnlyProperty<in T, out V> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
/**
* Base interface that can be used for implementing property delegates of read-write properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param T the type of object which owns the delegated property.
* @param V the type of the property value.
*/
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
/**
* Base interface that can be used for implementing property delegate providers.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your delegate provider has a method with the same signature.
*
* @param T the type of object which owns the delegated property.
* @param D the type of property delegates this provider provides.
*/
@SinceKotlin("1.4")
public fun interface PropertyDelegateProvider<in T, out D> {
/**
* Returns the delegate of the property for the given object.
*
* This function can be used to extend the logic of creating the object (e.g. perform validation checks)
* to which the property implementation is delegated.
*
* @param thisRef the object for which property delegate is requested.
* @param property the metadata for the property.
* @return the property delegate.
*/
public operator fun provideDelegate(thisRef: T, property: KProperty<*>): D
}
如果我们需要为 val 属性定义委托,我们就去实现 ReadOnlyProperty这个接口;如果我们需要为 var 属性定义委托,我们就去实现 ReadWriteProperty 这个接口。这样做的好处是,通过实现接口的方式,IntelliJ 可以帮我们自动生成 override 的 getValue、setValue 方法。以前面的代码为例,我们的 StringDelegate,也可以通过实现 ReadWriteProperty 接口来编写:
class StringDelegate(private var oldValue: String = "Hello") : ReadWriteProperty<Owner, String> {
override operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
return oldValue
}
override operator fun setValue(thisRef: Owner, property: KProperty<*>, newValue: String) {
oldValue = newValue
}
}
class Owner {
var text: String by StringDelegate("World")
init {
Log.d("TAG", "text:$text")
}
}
③提供委托(provideDelegate)
在前面二点的基础上,假如我们想在委托属性之前做点事情,我们可以使用 provideDelegate 来实现(看上面的Android源码)。看如下代码:
class StringDelegate(private var oldValue: String = "Hello") : ReadWriteProperty<Owner, String> {
override operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
return oldValue
}
override operator fun setValue(thisRef: Owner, property: KProperty<*>, newValue: String) {
oldValue = newValue
}
}
class SmartDelegator : PropertyDelegateProvider<Owner, StringDelegate> {
override fun provideDelegate(thisRef: Owner, property: KProperty<*>): StringDelegate {
return if (property.name == "log") {
StringDelegate("Log")
} else {
StringDelegate("Print")
}
}
}
class Owner {
var log: String by SmartDelegator()
var print: String by SmartDelegator()
init {
Log.d("TAG", "value:log:$log,print:$print")
}
}
//打印结果
value:log:Log,print:Print
通过 provideDelegate 这样的方式,我们不仅可以嵌套 Delegator,还可以根据不同的逻辑派发不同的Delegator。
四、委托实战案例
1、属性可见性封装
在java中我们防止外部修改整个集合的值是很困难的,但在kotlin中借助代理就很简单,如下:
class Model {
val data: List<String> by ::_data
private val _data: MutableList<String> = mutableListOf()
fun addValue() {
_data.add("hello")
_data.add("world")
}
}
data的集合类型为List,没有add和remove修改的方法。_data是私有的不对外暴露。_data修改的方法只有自己可以通过addValue调用。
2、数据与 View 的绑定
在 Android 当中,如果我们要对“数据”与View进行绑定,我们可以用 DataBinding,不过
DataBinding 太重了,也会影响编译速度。其实,除了 DataBinding 以外,我们还可以借助
Kotlin 的自定义委托属性来实现类似的功能。这种方式不一定完美,但也是一个有趣的思路。我们让TextView也提供代理,代码如下:
operator fun TextView.provideDelegate(value: Any?, property: KProperty<*>) =
object : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? = text?.toString()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
text = value
}
}
使用
val textView = findViewById<TextView>(R.id.tv)
var msg: String? by textView
textView.text = "hello"
Log.d("TAG", "msg:$msg") //msg:hello
msg = "world"
Log.d("TAG", "textView.text:${textView.text}") //textView.text:world
这个例子主要体现在思路,可以按自己的想法为对象(TextView)提供代理。
3、ViewModel 委托
在 Android 当中,我们会经常用到 ViewModel 来存储界面数据。同时,我们不会直接创建 ViewModel 的实例,而对应的,我们会使用委托的方式来实现。代码如下:
// MainActivity.kt
private val mainViewModel: MainViewModel by viewModels()
看下ViewModel的委托是如何实现的
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
原来viewModels是ComponentActivity的扩展函数,这里不讨论ViewModel是如何创建出来的,不是本节的讨论范畴,我们继续看ViewModelLazy方法,源码如下:
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> { //实现了Lazy接口
private var cached: VM? = null //缓存(是否创建过)
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) { //没有创建过
val factory = factoryProducer()
val store = storeProducer()
//创建了ViewModel
ViewModelProvider(
store,
factory,
extrasProducer()
).get(viewModelClass.java).also {
cached = it
}
} else { //创建过
viewModel
}
}
//是否被初始化过(没有被初始化过不能toString)
override fun isInitialized(): Boolean = cached != null
}
具体看代码里面的注释。
4、自己实现委托
我们模仿by viewModels实现自己的委托。首先是接口和子类:
interface Animal {
fun eat()
}
class Rabbit : Animal {
override fun eat() {
print("吃草")
}
fun getRabbit(): String = "Hello Rabbit"
}
具体的代理实现代码:
inline fun <reified AN : Animal> ComponentActivity.animals(): Lazy<AN> {
return AnimalLazy(AN::class)
}
class AnimalLazy<AN : Animal> constructor(private val animalClass: KClass<AN>) : Lazy<AN> {
private var cached: AN? = null
override val value: AN
get() {
return cached ?: animalClass.java.newInstance()
}
override fun isInitialized(): Boolean = cached != null
}
调用
private val rabbit: Rabbit by animals()
Log.d("TAG", rabbit.getRabbit()) //Hello Rabbit