Kotlin之代理、属性代理

589 阅读4分钟

我认为委托是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 官方文档