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