
有時(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)屬性 get 與 set 函數(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
}
像這樣修改以后,name 和 lastname 屬性就被委托給了 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ì)為 name 和 lastname 屬性生成持有 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。