6個能讓你的Kotlin代碼庫更有意思的“魔法糖”
語法糖會導(dǎo)致分號的悲劇。—— Alan J. Perlis
我們不斷地失去一些東西。其中一些東西相對來說會更重要,現(xiàn)在重新揀起來還不算太晚。Kotlin 語言為程序員的生活帶來了大量新的概念和特性,它們在日常開發(fā)中使用起來會很困難。我在生產(chǎn)環(huán)境中使用了兩年 Kotlin 之后,才感受到它帶來的快樂和滿足。這是怎么發(fā)生的?原因就在那些小小的語法糖中。
我會在本文中與你分析我最喜歡的 Kotlin 語法糖,它們是在我需要寫簡潔而魯棒 Android 應(yīng)用程序組件時發(fā)現(xiàn)的。為了讓這篇文章讀起來更輕松,我把它分成三個部分。在這第一部分中,你會看到密封類和 when() 控制流函數(shù)。愉快的開始吧!
擁抱“模式匹配”的密封類
最近我的工作中有機會使用 Swift。我不僅要審核代碼,還要將其中一些組件翻譯成 Kotlin 實現(xiàn)。我讀的代碼越多,就越感到驚訝。最對我來說,最吸引人的特性是枚舉。可惜 Kotlin 的枚舉并不太靈活,我不得不挖掘合適的替代品: 密封類 。
密封類在編程界并不是什么新鮮玩意兒。事實上,密封類是一個非常知名的語言概念。Kotlin 引入了 sealed 關(guān)鍵字,它可用于類聲明,表示對類層次結(jié)構(gòu)的限制。某個值可以是有限類型中的一個,但它不能是其它類型。簡單地說,你可以使用密封類來代替枚舉,甚至做更多事情。
來看看下面的示例代碼。
- sealed class Response
- data class Success(val body: String): Response()
- data class Error(val code: Int, val message: String): Response()
- object Timeout: Response()
乍一看,這些代碼除只是聲明了一些簡單的繼承關(guān)系,但步步深入,就會提示一個諒人的真相。為 Response 類添加的 sealed 關(guān)鍵字到底起到了什么作用呢?提示這個問題最好的方法是使用 IntelliJ IDEA Kotlin Bytecode 工具。
第一 步。查看 Kotlin 字節(jié)碼 (Kotlin Bytecode)
第二步。將 Kotlin 字節(jié)碼反編譯成 Java 代碼
經(jīng)過這樣非常簡單地翻譯,你可以看到 Kotlin 代碼對應(yīng)的 Java 代碼呈現(xiàn)。
- public abstract class Response {
- private Response() {
- }
- // $FF: synthetic method
- public Response(DefaultConstructorMarker $constructor_marker) {
- this();
- }
- }
你可能已經(jīng)猜到了,密封類專們用于繼承,所以它們是抽象的。不過他們變得與枚舉相似的?在這里,Kotlin 編譯器做了大量的工作,讓你可以在 when() 函數(shù)中將 Response 的子類用作分支。此外,Kotlin 提供了很大的靈活性來允許對密封類的繼承結(jié)構(gòu)可以被當(dāng)作數(shù)據(jù)聲明甚至對象來使用。
- fun sugar(response: Response) = when (response) {
- is Success -> ...
- is Error -> ...
- Timeout -> ...
- }
它不僅提供了非常徹底的表達式,還提供了自動類型轉(zhuǎn)換,因此你可以在不需要額外的轉(zhuǎn)換的情況下使用 Response 實例。
- fun sugar(response: Response) = when (response) {
- is Success -> println(response.body)
- is Error -> println("${response.code} ${response.message}")
- Timeout -> println(response.javaClass.simpleName)
- }
你能想象一下,如果沒有一個 sealed 的功能,或者根本沒有 Kotlin ,它可能看起來是那么的丑陋和復(fù)雜?如果你忘記了 Java 語言的一些特性,請再次使用 IntelliJ IDEA Kotlin Bytecode ,但要坐下來使用 - 這可能會讓你暈倒。
- public final void sugar(@NotNull Response response) {
- Intrinsics.checkParameterIsNotNull(response, "response");
- String var3;
- if (response instanceof Success) {
- var3 = ((Success)response).getBody();
- System.out.println(var3);
- } else if (response instanceof Error) {
- var3 = "" + ((Error)response).getCode() + ' ' + ((Error)response).getMessage();
- System.out.println(var3);
- } else {
- if (!Intrinsics.areEqual(response, Timeout.INSTANCE)) {
- throw new NoWhenBranchMatchedException();
- }
- var3 = response.getClass().getSimpleName();
- System.out.println(var3);
- }
- }
總結(jié)一下,我很高興在這種情況下使用 sealed 關(guān)鍵字,因為它讓我以類似于 Swift 的方式塑造我的 Kotlin 代碼。
使用 when()函數(shù)來排列
由于你已經(jīng)看到了 when()在 sealed 類中的用法,我決定再分享更多強大的功能。 想象一下,你必須實現(xiàn)一個接受兩個 enums 并產(chǎn)生一個不可變狀態(tài)的函數(shù)。
- enum class Employee {
- DEV_LEAD,
- SENIOR_ENGINEER,
- REGULAR_ENGINEER,
- JUNIOR_ENGINEER
- }
- enum class Contract {
- PROBATION,
- PERMANENT,
- CONTRACTOR,
- }
enum class Employee 描述了在公司 XYZ 中可以找到的所有角色, enum class Contract 包含所有類型的雇傭合同。 基于這兩個 enums ,你應(yīng)該返回一個正確的 SafariBookAccess 。 而且,你的函數(shù)必須產(chǎn)生給定 enum 的所有排列的狀態(tài)。 第一步,我們來創(chuàng)建狀態(tài)生成函數(shù)的簽名。
- fun access(employee: Employee,
- contract: Contract): SafariBookAccess
現(xiàn)在是時候定義 SafariBooksAccess 結(jié)構(gòu)體了,因為你已了解 sealed 關(guān)鍵字,這是使用它最適合的時機。封裝 SafariBookAccess 并不是必須的,但它是封裝不同情景下的 SafariBookAccess 的不同狀態(tài)的好方式。
- sealed class SafariBookAccess
- data class Granted(val expirationDate: DateTime) : SafariBookAccess()
- data class NotGranted(val error: AssertionError) : SafariBookAccess()
- data class Blocked(val message: String) : SafariBookAccess()
那么隱藏在 access() 函數(shù)后面的主要意圖是什么?全排列!讓我們羅列下。
- fun access(employee: Employee,
- contract: Contract): SafariBookAccess {
- return when (employee) {
- SENIOR_ENGINEER -> when (contract) {
- PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))
- PERMANENT -> Granted(DateTime())
- CONTRACTOR -> Granted(DateTime())
- }
- REGULAR_ENGINEER -> when (contract) {
- PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))
- PERMANENT -> Granted(DateTime())
- CONTRACTOR -> Blocked("Access blocked for $contract.")
- }
- JUNIOR_ENGINEER -> when (contract) {
- PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))
- PERMANENT -> Blocked("Access blocked for $contract.")
- CONTRACTOR -> Blocked("Access blocked for $contract.")
- }
- else -> throw AssertionError()
- }
- }
這個代碼很完美,但你能讓它更像 Kotlin 嗎?當(dāng)你每天對同事的 PR/MR 進行審查時會有什么建議嗎?你可能會寫一些這樣的評論:
- 太多 when() 函數(shù)。使用 Pair 來避免嵌套。
- 改變枚舉參數(shù)的順序,定義 Pair() 對象來讓它更易讀。
- 合并重復(fù)的 return。
- 改為一個表達式函數(shù)。
- fun access(contract: Contract,
- employee: Employee) = when (Pair(contract, employee)) {
- Pair(PROBATION, SENIOR_ENGINEER),
- Pair(PROBATION, REGULAR_ENGINEER),
- Pair(PROBATION, JUNIOR_ENGINEER) -> NotGranted(AssertionError("Access not allowed on probation contract."))
- Pair(PERMANENT, SENIOR_ENGINEER),
- Pair(PERMANENT, REGULAR_ENGINEER),
- Pair(PERMANENT, JUNIOR_ENGINEER),
- Pair(CONTRACTOR, SENIOR_ENGINEER) -> Granted(DateTime(1))
- Pair(CONTRACTOR, REGULAR_ENGINEER),
- Pair(CONTRACTOR, JUNIOR_ENGINEER) -> Blocked("Access for junior contractors is blocked.")
- else -> throw AssertionError("Unsupported case of $employee and $contract")
- }
現(xiàn)在它看起來更整潔,但 Kotlin 還有語法糖可以完全省略對 Pair 的定義。棒!
- fun access(contract: Contract,
- employee: Employee) = when (contract to employee) {
- PROBATION to SENIOR_ENGINEER,
- PROBATION to REGULAR_ENGINEER -> NotGranted(AssertionError("Access not allowed on probation contract."))
- PERMANENT to SENIOR_ENGINEER,
- PERMANENT to REGULAR_ENGINEER,
- PERMANENT to JUNIOR_ENGINEER,
- CONTRACTOR to SENIOR_ENGINEER -> Granted(DateTime(1))
- CONTRACTOR to REGULAR_ENGINEER,
- PROBATION to JUNIOR_ENGINEER,
- CONTRACTOR to JUNIOR_ENGINEER -> Blocked("Access for junior contractors is blocked.")
- else -> throw AssertionError("Unsupported case of $employee and $contract")
- }
這個結(jié)構(gòu)讓我的生活變得輕松,也讓 Kotlin 代碼讀寫變得容易,我希望你也覺得這很有用。但它是不是不能用于三元組呢?答案是肯定的。
- Triple(enum1, enum2, enum3) == enum1 to enum2 to enum3
以上就是第 1 部分的全部內(nèi)容,如果你仍然很有興趣,請繼續(xù)閱讀第 2 部分。干杯!