有时候,实现一些工作的办法是将它们委托给他人。这里不是在建议您将本人的工作委托给敌人去做,而是在说将一个对象的工作委托给另一个对象。

当然,委托在软件行业不是什么陈腐名词。委托 (Delegation) 是一种设计模式,在该模式中,对象会委托一个助手 (helper) 对象来解决申请,这个助手对象被称为代理。代理负责代表原始对象解决申请,并使后果可用于原始对象。

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 接口中的所有函数。与此同时,当初您能够增加本人的行为了。

工作原理

让咱们看看这所有是如何工作的。如果您去查看 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 关键字进行属性代理。通过应用属性代理,代理会负责解决对应属性 getset 函数的调用。这一个性在您须要在其余对象间复用 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}

像这样批改当前,namelastname 属性就被委托给了 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 编译器会为 namelastname 属性生成持有 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。