9. Kotlin 函數聲明和擴展(extension)
- 2020 年 3 月 30 日
- 筆記
1. Java 的老朋友 Utils 工具類
Utils 工具類是無構造參數的 static 方法集合,用於擴展某個對象的功能,如 MathUtils,ToastUtils,FIleUtils,StringUtils, LogUtils。Utils 類在一定程度上減少了重複程式碼的問題,它是成本最低的 DRY(Don』t repeat yourself)實踐。
Utils 工具類實在太常見了,以至於很多開發者都不曾質疑他的合理性。但 Utils 實際上是反 OOP (面向對象模式)的妥協產物。我們從程式碼設計的角度看,Utils 方法是 static 的,沒有 OOP 的繼承,重寫,抽象的特性(static 本身就是反 OOP 的)。且 Utils 違反了單一職責,一個類應該包含其屬性和所有操作方法。而 Utils 實現的方法並不在這個類內。
而從使用者的角度,使用者必須預先知道這個 Utils 工具類的存在,他能使用為這個類添加的擴展方法。 在實際項目實踐中,這個條件往往是缺失的,因為在團隊開發中,個人無法掌握所有程式碼,因為不知道這個程式碼已經有人實現過了,導致大家都實現了自己的 Utils。一個工程里同一個類的 Utils 往往會有好幾個。
但存在必然是合理的。我自己就是一個寫 Utils 的老司機。從個人角度來看,讓我使用 Utils 而不是對象繼承的原因,主要是因為:
1. 無法繼承/重寫這些類及其方法,只能通過 Utils 擴展; 2. 繼承一個類比抽取程式碼塊封裝為函數的實現成本+替換成本高; 3. Utils 絕大情況下只是用來存儲程式碼塊,需求非常穩定,無需面向對象。
依賴的類是 SDK 提供的時候 Utils 往往是不可避免的。且使用 Utils 的場景里很少會用到面向對象的特性,那麼沒有面向對象的缺點也並沒有那麼嚴重了。那麼拋開 Utils 的設計缺點,我們是否可以避免使用上的缺點?Kotlin 提供的解決方法就是擴展(extension)。
2. Kotlin 擴展的使用和實現分析
聲明一個 Kotlin 擴展如下:
// StringUtils.kt fun String.appendHaha(): String { return this + "haha" }
它與普通的方法聲明很接近,只是方法名前多了一個類名,來表示其歸屬的類。擴展聲明為頂層聲明的時候可以被外部調用(是的,因為函數是一等公民,在方法內部也可以聲明擴展方法)。
在函數體內用 this 來引用調用的實例,屬性和方法的訪問許可權與普通調用一致。一致的意思是和你正常在其他方法內部調用的許可權一致,並不會因為是擴展聲明就可以訪問 private/propect 許可權的屬性和方法。這是因為擴展聲明在位元組碼層面上其實是 static 方法。下面是appendHaha
對應 jvm 位元組碼的反編譯結果:
public class StringUtilsKt { @NotNull public static final String haha(@NotNull String $this$haha) { Intrinsics.checkParameterIsNotNull($this$haha, "$this$haha"); return $this$haha + "haha"; } }
所以從 Java 的角度來看,Kotlin 的擴展方法和 Utils 的調用沒有差別,都是調用類的 static 方法然後傳入操作的參數。實際上 Java 想要調用 Kotlin 的擴展方法也確實是這樣調用的。
擴展方法的調用和實例方法調用一致,在調用者角度沒有區別。 Android Studio 會自動提示對應類所有的擴展方法,且擴展方法的顏色(黃色)會和普通實例方法(白色)區分開來。
Kotlin 的擴展特性和 objective-C 的 category 特性功能非常相似,都是為一個現有的類添加方法(且只能添加方法),只是程式碼組織結構上有些許差異。但 objective-C 的 category 特性是 runtime 特性,Kotlin 擴展的實現更接近語法糖。
3. 總結
Kotlin 擴展依然沒有解決 Utils 類的設計缺點。就像 Kotlin companion object 對 Java static,Kotlin Int 對 Java int,Kotlin property 對 Java field 一樣,Kotlin 擴展是 Kotlin 對 Java 不完全面向對象的「清理」,使 Kotlin 更接近完全面向對象。相比 Utils 工具類,Kotlin 擴展特性能有效減少開發者心智負擔和溝通成本,從而提高開發效率。