Kotlin Vocabulary | Kotlin 委托代理

image

有時(shí)候,完成一些工作的方法是將它們委托給別人。這里不是在建議您將自己的工作委托給朋友去做,而是在說(shuō)將一個(gè)對(duì)象的工作委托給另一個(gè)對(duì)象。

當(dāng)然,委托在軟件行業(yè)不是什么新鮮名詞。委托 (Delegation) 是一種設(shè)計(jì)模式,在該模式中,對(duì)象會(huì)委托一個(gè)助手 (helper) 對(duì)象來(lái)處理請(qǐng)求,這個(gè)助手對(duì)象被稱為代理。代理負(fù)責(zé)代表原始對(duì)象處理請(qǐng)求,并使結(jié)果可用于原始對(duì)象。

Kotlin 不僅支持類和屬性的代理,其自身還包含了一些內(nèi)建代理,從而使得實(shí)現(xiàn)委托變得更加容易。

類代理

這里舉個(gè)例子,您需要實(shí)現(xiàn)一個(gè)同 ArrayList 基本相同的用例,唯一的不同是此用例可以恢復(fù)最后一次移除的項(xiàng)目?;旧?,實(shí)現(xiàn)此用例您所需要的就是一個(gè)同樣功能的 ArrayList,以及對(duì)最后移除項(xiàng)目的引用。

實(shí)現(xiàn)這個(gè)用例的一種方式,是繼承 ArrayList 類。由于新的類繼承了具體的 ArrayList 類而不是實(shí)現(xiàn) MutableList 接口,因此它與 ArrayList 的實(shí)現(xiàn)高度耦合。

如果只需要覆蓋 remove() 函數(shù)來(lái)保持對(duì)已刪除項(xiàng)目的引用,并將 MutableList 的其余空實(shí)現(xiàn)委托給其他對(duì)象,那該有多好啊。為了實(shí)現(xiàn)這一目標(biāo),Kotlin 提供了一種將大部分工作委托給一個(gè)內(nèi)部 ArrayList 實(shí)例并且可以自定義其行為的方式,并為此引入了一個(gè)新的關(guān)鍵字: by。

讓我們看看類代理的工作原理。當(dāng)您使用 by 關(guān)鍵字時(shí),Kotlin 會(huì)自動(dòng)生成使用 innerList 實(shí)例作為代理的代碼:

<!-- 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 關(guān)鍵字告訴 Kotlin 將 MutableList 接口的功能委托給一個(gè)名為 innerList 的內(nèi)部 ArrayList。通過(guò)橋接到內(nèi)部 ArrayList 對(duì)象方法的方式,ListWithTrash 仍然支持 MutableList 接口中的所有函數(shù)。與此同時(shí),現(xiàn)在您可以添加自己的行為了。

工作原理

讓我們看看這一切是如何工作的。如果您去查看 ListWithTrash 字節(jié)碼所反編譯出的 Java 代碼,您會(huì)發(fā)現(xiàn) Kotlin 編譯器其實(shí)創(chuàng)建了一些包裝函數(shù),并用它們調(diào)用內(nèi)部 ArrayList 對(duì)象的相應(yīng)函數(shù):

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 編譯器使用了另一種設(shè)計(jì)模式——裝飾者模式。在裝飾者模式中,裝飾者類與被裝飾類使用同一接口。裝飾者會(huì)持有一個(gè)目標(biāo)類的內(nèi)部引用,并且包裝 (或者裝飾) 接口提供的所有公共方法。

在您無(wú)法繼承特定類型時(shí),委托模式就顯得十分有用。通過(guò)使用類代理,您的類可以不繼承于任何類。相反,它會(huì)與其內(nèi)部的源類型對(duì)象共享相同的接口,并對(duì)該對(duì)象進(jìn)行裝飾。這意味著您可以輕松切換實(shí)現(xiàn)而不會(huì)破壞公共 API。

屬性代理

除了類代理,您還可以使用 by 關(guān)鍵字進(jìn)行屬性代理。通過(guò)使用屬性代理,代理會(huì)負(fù)責(zé)處理對(duì)應(yīng)屬性 getset 函數(shù)的調(diào)用。這一特性在您需要在其他對(duì)象間復(fù)用 getter/setter 邏輯時(shí)十分有用,同時(shí)也能讓您可以輕松地對(duì)簡(jiǎn)單支持字段的功能進(jìn)行擴(kuò)展。

讓我們假設(shè)您有一個(gè) Person 類型,定義如下:

class Person(var name: String, var lastname: String)

該類型的 name 屬性有一些格式化需求。當(dāng) name 被賦值時(shí),您想要確保將第一個(gè)字母大寫的同時(shí)將其余字母格式化為小寫。另外,在更新 name 的值時(shí),您想要自動(dòng)增加 updateCount 屬性。

您可以像下面這樣實(shí)現(xiàn)這一功能:

<!-- 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
}

上述代碼當(dāng)然是可以解決問(wèn)題的,但若需求發(fā)生改變,比如您想要在 lastname 的值發(fā)生改變時(shí)也增加 updateCount 的話會(huì)怎樣?您可以復(fù)制粘貼這段邏輯并實(shí)現(xiàn)一個(gè)自定義 setter,但這樣一來(lái),您會(huì)發(fā)現(xiàn)自己為所有屬性編寫了完全相同的 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
}

兩個(gè) setter 方法幾乎完全相同,這意味著這里的代碼可以進(jìn)行優(yōu)化。通過(guò)使用屬性代理,我們可以將 getter 和 setter 委托給屬性,從而可以復(fù)用代碼。

與類代理相同,您可以使用 by 來(lái)代理一個(gè)屬性,Kotlin 會(huì)在您使用屬性語(yǔ)法時(shí)生成代碼來(lái)使用代理。

<!-- 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 類。現(xiàn)在讓我們來(lái)看看 FormatDelegate 的代碼。如果您只需要委托 getter,那么代理類需要實(shí)現(xiàn) ReadProperty<Any?, String>;而如果 getter 與 setter 都要委托,則代理類需要實(shí)現(xiàn) ReadWriteProperty<Any?, String>。在我們的例子中,FormatDelegate 需要實(shí)現(xiàn) ReadWriteProperty<Any?, String>,因?yàn)槟朐谡{(diào)用 setter 時(shí)執(zhí)行格式化操作。

<!-- 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()
   }
}

您可能已經(jīng)注意到,getter 和 setter 函數(shù)中有兩個(gè)額外參數(shù)。第一個(gè)參數(shù)是 thisRef,代表了包含該屬性的對(duì)象。thisRef 可用于訪問(wèn)對(duì)象本身,以用于檢查其他屬性或調(diào)用其他類函數(shù)一類的目的。第二個(gè)參數(shù)是 KProperty<*>,可用于訪問(wèn)被代理的屬性上的元數(shù)據(jù)。

回頭看一看需求,讓我們使用 thisRef 來(lái)訪問(wèn)和增加 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()
}

工作原理

為了理解其工作原理,讓我們來(lái)看看反編譯出的 Java 代碼。Kotlin 編譯器會(huì)為 namelastname 屬性生成持有 FormatDelegate 對(duì)象私有引用的代碼,以及包含您所添加邏輯的 getter 和 setter。

編譯器還會(huì)創(chuàng)建一個(gè) KProperty[] 用于存放被代理的屬性。如果您查看了為 name 屬性所生成的 getter 和 setter,就會(huì)發(fā)現(xiàn)它的實(shí)例存儲(chǔ)在了索引為 0 的位置, 同時(shí) lastname 被存儲(chǔ)在索引為 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);
  }
  //...
}

通過(guò)這一技巧,任何調(diào)用者都可以通過(guò)常規(guī)的屬性語(yǔ)法訪問(wèn)代理屬性。

person.lastname = “Smith” 
// 調(diào)用生成的 setter,增加數(shù)量
 
println(“Update count is $person.count”)

Kotlin 不僅支持委托模式的實(shí)現(xiàn),同時(shí)還在標(biāo)準(zhǔn)庫(kù)中提供了內(nèi)建的代理,我們將在另一篇文章中進(jìn)行詳細(xì)地介紹。

代理可以幫您將任務(wù)委托給其他對(duì)象,并提供更好的代碼復(fù)用性。Kotlin 編譯器會(huì)創(chuàng)建代碼以使您可以無(wú)縫使用代理。Kotlin 使用簡(jiǎn)單的 by 關(guān)鍵字語(yǔ)法來(lái)代理屬性或類。內(nèi)部實(shí)現(xiàn)上,Kotlin 編譯器會(huì)生成支持代理所需的所有代碼,而不會(huì)暴露任何公共 API 的修改。簡(jiǎn)而言之,Kotlin 會(huì)生成和維護(hù)所有代理所需的樣板代碼,換句話說(shuō),您可以將您的工作放心地委托給 Kotlin。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容