Kotlin Vocabulary | Kotlin 委託代理

有時候,完成一些工做的方法是將它們委託給別人。這裏不是在建議您將本身的工做委託給朋友去作,而是在說將一個對象的工做委託給另外一個對象。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 關鍵字進行屬性代理。經過使用屬性代理,代理會負責處理對應屬性 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。

相關文章
相關標籤/搜索