有时候,完成一些工做的方法是将它们委托给别人。这里不是在建议您将本身的工做委托给朋友去作,而是在说将一个对象的工做委托给另外一个对象。java
固然,委托在软件行业不是什么新鲜名词。委托 (Delegation) 是一种设计模式,在该模式中,对象会委托一个助手 (helper) 对象来处理请求,这个助手对象被称为代理。代理负责表明原始对象处理请求,并使结果可用于原始对象。设计模式
Kotlin 不只支持类和属性的代理,其自身还包含了一些内建代理,从而使得实现委托变得更加容易。api
这里举个例子,您须要实现一个同 ArrayList
基本相同的用例,惟一的不一样是此用例能够恢复最后一次移除的项目。基本上,实现此用例您所须要的就是一个一样功能的 ArrayList
,以及对最后移除项目的引用。ide
实现这个用例的一种方式,是继承 ArrayList
类。因为新的类继承了具体的 ArrayList
类而不是实现 MutableList
接口,所以它与 ArrayList
的实现高度耦合。函数
若是只须要覆盖 remove()
函数来保持对已删除项目的引用,并将 MutableList
的其他空实现委托给其余对象,那该有多好啊。为了实现这一目标,Kotlin 提供了一种将大部分工做委托给一个内部 ArrayList
实例而且能够自定义其行为的方式,并为此引入了一个新的关键字: by。优化
让咱们看看类代理的工做原理。当您使用 by
关键字时,Kotlin 会自动生成使用 innerList
实例做为代理的代码:this
<!-- 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
接口中的全部函数。与此同时,如今您能够添加本身的行为了。spa
让咱们看看这一切是如何工做的。若是您去查看 ListWithTrash
字节码所反编译出的 Java 代码,您会发现 Kotlin 编译器其实建立了一些包装函数,并用它们调用内部 ArrayList
对象的相应函数:设计
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() { this((List)null, 1, (DefaultConstructorMarker)null); } public int getSize() { return this.innerList.size(); } // $FF: 桥接方法 public final int size() { return this.getSize(); } //…... }
注意: 为了在生成的代码中支持类代理,Kotlin 编译器使用了另外一种设计模式——装饰者模式。在装饰者模式中,装饰者类与被装饰类使用同一接口。装饰者会持有一个目标类的内部引用,而且包装 (或者装饰) 接口提供的全部公共方法。
在您没法继承特定类型时,委托模式就显得十分有用。经过使用类代理,您的类能够不继承于任何类。相反,它会与其内部的源类型对象共享相同的接口,并对该对象进行装饰。这意味着您能够轻松切换实现而不会破坏公共 API。代理
除了类代理,您还能够使用 by
关键字进行属性代理。经过使用属性代理,代理会负责处理对应属性 get
与 set
函数的调用。这一特性在您须要在其余对象间复用 getter/setter 逻辑时十分有用,同时也能让您能够轻松地对简单支持字段的功能进行扩展。
让咱们假设您有一个 Person 类型,定义以下:
class Person(var name: String, var lastname: String)
该类型的 name
属性有一些格式化需求。当 name
被赋值时,您想要确保将第一个字母大写的同时将其他字母格式化为小写。另外,在更新 name
的值时,您想要自动增长 updateCount
属性。
您能够像下面这样实现这一功能:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class Person(name: String, var lastname: String) { var name: String = name set(value) { name = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0 }
上述代码固然是能够解决问题的,但若需求发生改变,好比您想要在 lastname
的值发生改变时也增长 updateCount
的话会怎样?您能够复制粘贴这段逻辑并实现一个自定义 setter,但这样一来,您会发现本身为全部属性编写了彻底相同的 setter。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class Person(name: String, lastname: String) { var name: String = name set(value) { name = value.toLowerCase().capitalize() updateCount++ } var lastname: String = lastname set(value) { lastname = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0 }
两个 setter 方法几乎彻底相同,这意味着这里的代码能够进行优化。经过使用属性代理,咱们能够将 getter 和 setter 委托给属性,从而能够复用代码。
与类代理相同,您能够使用 by
来代理一个属性,Kotlin 会在您使用属性语法时生成代码来使用代理。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class Person(name: String, lastname: String) { var name: String by FormatDelegate() var lastname: String by FormatDelegate() var updateCount = 0 }
像这样修改之后,name
和 lastname
属性就被委托给了 FormatDelegate
类。如今让咱们来看看 FormatDelegate
的代码。若是您只须要委托 getter,那么代理类须要实现 ReadProperty<Any?, String>
;而若是 getter 与 setter 都要委托,则代理类须要实现 ReadWriteProperty<Any?, String>
。在咱们的例子中,FormatDelegate
须要实现 ReadWriteProperty<Any?, String>
,由于您想在调用 setter 时执行格式化操做。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.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?, property: KProperty<*>, value: String ) { formattedString = value.toLowerCase().capitalize() } }
您可能已经注意到,getter 和 setter 函数中有两个额外参数。第一个参数是 thisRef
,表明了包含该属性的对象。thisRef
可用于访问对象自己,以用于检查其余属性或调用其余类函数一类的目的。第二个参数是 KProperty<*>
,可用于访问被代理的属性上的元数据。
回头看一看需求,让咱们使用 thisRef
来访问和增长 updateCount
属性:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> override fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) { if (thisRef is Person) { thisRef.updateCount++ } formattedString = value.toLowerCase().capitalize() }
为了理解其工做原理,让咱们来看看反编译出的 Java 代码。Kotlin 编译器会为 name
和 lastname
属性生成持有 FormatDelegate
对象私有引用的代码,以及包含您所添加逻辑的 getter 和 setter。
编译器还会建立一个 KProperty[]
用于存放被代理的属性。若是您查看了为 name
属性所生成的 getter 和 setter,就会发现它的实例存储在了索引为 0 的位置, 同时 lastname
被存储在索引为 1 的位置。
public final class Person { // $FF: 合成字段 static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))}; @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.checkParameterIsNotNull(var1, "<set-?>"); this.name$delegate.setValue(this, $$delegatedProperties[0], var1); } //... }
经过这一技巧,任何调用者均可以经过常规的属性语法访问代理属性。
person.lastname = “Smith” // 调用生成的 setter,增长数量 println(“Update count is $person.count”)
Kotlin 不只支持委托模式的实现,同时还在标准库中提供了内建的代理,咱们将在另外一篇文章中进行详细地介绍。
代理能够帮您将任务委托给其余对象,并提供更好的代码复用性。Kotlin 编译器会建立代码以使您能够无缝使用代理。Kotlin 使用简单的 by
关键字语法来代理属性或类。内部实现上,Kotlin 编译器会生成支持代理所需的全部代码,而不会暴露任何公共 API 的修改。简而言之,Kotlin 会生成和维护全部代理所需的样板代码,换句话说,您能够将您的工做放心地委托给 Kotlin。