我认为委托是kotlin中比较重要且有点小难的概念。这是我自己看文章过程中以及在自己的代码中使用的一些总结。下面开始进入正题。
委托(Delegation) 是一种设计模式(代理模式),对象会委托一个助手(hepler)对象来处理请求,这个助手对象被称为代理。代理负责代表原始对象处理请求,并使结果可用于原始对象。
Kotlin不仅支持类和属性的代理,其自身还包含了一些内建代理,从而使实现委托变得更加容易。
类代理
用法
这里举个例子,您需要实现一个同 ArrayList 基本相同的用例,唯一的不同是此用例可以恢复最后一次移除的项目。基本上,实现此用例您所需要的就是一个同样功能的 ArrayList,以及对最后移除项目的引用。 实现这个用例的一种方式,是继承 ArrayList 类。由于新的类继承了具体的 ArrayList 类而不是实现 MutableList 接口,因此它与 ArrayList 的实现高度耦合。 如果只需要覆盖 remove() 函数来保持对已删除项目的引用,并将 MutableList 的其余空实现委托给其他对象,那该有多好啊。为了实现这一目标,Kotlin 提供了一种将大部分工作委托给一个内部 ArrayList 实例并且可以自定义其行为的方式,并为此引入了一个新的关键字: by。 让我们看看类代理的工作原理。当您使用 by 关键字时,Kotlin 会自动生成使用 innerList 实例作为代理的代码:
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class ListWithTrash <T>(private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList {
var deletedItem : T? = null
override fun remove(element: T): Boolean {
deletedItem = element
return innerList.remove(element)
}
fun recover(): T? {
return deletedItem
}
}
by 关键字告诉 Kotlin 将 MutableList 接口的功能委托给一个名为 innerList 的内部 ArrayList。通过桥接到内部 ArrayList 对象方法的方式,ListWithTrash 仍然支持 MutableList 接口中的所有函数。与此同时,现在您可以添加自己的行为了。
来看一个简单的例子
interface Base {
fun print()
fun printMessage()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
Log.i("zbt", "result: $x")
}
override fun printMessage() {
Log.i("zbt", "printMessage: $x")
}
}
class Derived(b: Base) : Base by b {
override fun print() {
Log.i("zbt", "delegation")
}
}
原理
可以看下ListWithTrash的kotlin 字节码反编译成java类。可以看到的是,ListWithTrash实现了Collection类,然后内部使用了包装函数,然后让包装函数调用ArrayList的相应功能。
这样可以看到,by 关键字告诉 Kotlin 将 MutableList 接口的功能委托给一个名为 innerList 的内部 ArrayList。通过桥接到内部 ArrayList 对象方法的方式,ListWithTrash 仍然支持 MutableList 接口中的所有函数。与此同时,现在您可以添加自己的行为了。
// 元数据,里面包含了类的信息
@Metadata(……)
public final class ListWithTrash implements Collection, KMutableCollection {
@Nullable
private Object deletedItem;
private final List innerList;
@Nullable
public final Object getDeletedItem() {
return this.deletedItem;
}
public final void setDeletedItem(@Nullable Object var1) {
this.deletedItem = var1;
}
public boolean remove(Object element) {
this.deletedItem = element;
return this.innerList.remove(element);
}
@Nullable
public final Object recover() {
return this.deletedItem;
}
public ListWithTrash(@NotNull List innerList) {
Intrinsics.checkNotNullParameter(innerList, "innerList");
super();
this.innerList = innerList;
}
// $FF: synthetic method 合成方法
public ListWithTrash(List var1, int var2, DefaultConstructorMarker var3) {
if ((var2 & 1) != 0) {
var1 = (List)(new ArrayList());
}
this(var1);
}
public ListWithTrash() {
this((List)null, 1, (DefaultConstructorMarker)null);
}
public int getSize() {
return this.innerList.size();
}
// $FF: bridge method
public final int size() {
return this.getSize();
}
……
}
注意: 为了在生成的代码中支持类代理,Kotlin 编译器使用了另一种设计模式——装饰者模式。在装饰者模式中,装饰者类与被装饰类使用同一接口。装饰者会持有一个目标类的内部引用,并且包装 (或者装饰) 接口提供的所有公共方法。
我们可以看一下,平常封装的BaseActivity使用协程反编译是怎么样的。
class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {
}
// 反编译后的代码
public final class BaseActivity extends AppCompatActivity implements CoroutineScope {
// $FF: synthetic field
private final CoroutineScope $$delegate_0 = CoroutineScopeKt.MainScope();
private HashMap _$_findViewCache;
@NotNull
public CoroutineContext getCoroutineContext() {
return this.$$delegate_0.getCoroutineContext();
}
……
}
这里可以看到,MainScope是一个顶层函数,将Coroutines的实现委托给MainScope函数,最终由ContextScope实现。
在您无法继承特定类型时,委托模式就显得十分有用。通过使用类代理,您的类可以不继承于任何类。相反,它会与其内部的源类型对象共享相同的接口,并对该对象进行装饰。这意味着您可以轻松切换实现而不会破坏公共 API。
使用总结:
- 继承或者实现接口可以通过委托模式
- 无论是通过传值还是通过MainScope顶层函数这种方式,最终都是子类去实现该函数然后使用代理类经过包装后被调用。
属性代理
用法
属性也可以被代理的,属性代理主要是代理属性的get和set函数,当然也是通过关键字 by。
class Person(name: String, lastname: String) {
var name: String by FormatDelegate()
var lastname: String by FormatDelegate()
var updateCount = 0
}
class FormatDelegate : ReadWriteProperty<Any?, String> {
// 代理的属性
private var formattedString: String = ""
override fun getValue(
thisRef: Any?,
property: KProperty<*>
): String {
return formattedString
}
override fun setValue(
thisRef: Any?, // 包含该属性的对象就是Person
property: KProperty<*>, // 可用于访问被代理的属性上的元数据
value: String
) {
if (thisRef is Person) { // 类型转换后可以使用Person对象
thisRef.updateCount++
}
formattedString = value.toLowerCase().capitalize()
}
}
Person类,其中两个属性name和lastName都被FormateDelegate()代理,在调用的name或者lastName的时候,我们其实使用的是FormateDelegate中的setValue()和VgetValue()。
这两个接口是专门用于实现属性代理的,ReadOnlyProperty用于val修饰的属性,ReadWriteProperty用于var修饰的属性。
public fun interface ReadOnlyProperty<in T, out V> {
// thisRef 表示包含该属性的对象, property 用于访问被代理的属性上的元数据
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
原理
可以看到属性委托反编译生成代码和类代理生成的代码类似。编译器会创建一个 KProperty[] 用于存放被代理的属性。如果您查看了为 name 属性所生成的 getter 和 setter,就会发现它的实例存储在了索引为 0 的位置, 同时 lastname 被存储在索引为 1 的位置。
public final class Person {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Person.class, "name", "getName()Ljava/lang/String;", 0)), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Person.class, "lastname", "getLastname()Ljava/lang/String;", 0))};
@NotNull
private final FormatDelegate name$delegate;
@NotNull
private final FormatDelegate lastname$delegate;
private int updateCount;
@NotNull
public final String getName() {
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], (String)var1);
}
……
public Person(@NotNull String name, @NotNull String lastname) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(lastname, "lastname");
super();
this.name$delegate = new FormatDelegate();
this.lastname$delegate = new FormatDelegate();
}
}
lazy()
用于val声明属性,属性第一次使用get()时,执行该lambda进行赋值,返回lambda中的最后一行代码的值,如下:
class Person() {
val name: String by lazy { "zbt" }
}
val apiService: ApiService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
NetworkApi.INSTANCE.getApi(ApiService::class.java, ApiService.SERVER_URL)
}
lazy()的三种初始化线程模式:
public enum class LazyThreadSafetyMode {
SYNCHRONIZED,// 默认模式,只能在一个线程中进行修改,其它线程只能使用,使用的是对象锁
PUBLICATION,// 允许多个线程同时进入,但是只有一个线程是能修改
NONE,// 线程不安全模式
}
如果不存在线程安全模式,选择NONE比较好。
Delegates.observable()
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
用来监听var变量发送变化的代理,需要在observable中赋初始值
var name: String by Delegates.observable("hhh") { property, old, new ->
println("$old -> $new")
}
代理其他属性
条件:
- a top-level property
- a member or an extension property of the same class
- a member or an extension property of another class
示例
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
过时示例:
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// Notification: 'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}
存储属性到map
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
//使用MutableMap替换只读map
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
本地代理属性
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
参考
谷歌开发者的Kotlin Vocabulary | Kotlin 委托代理
Kotlin 官方文档