iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 17
0
Software Development

Kotlin 島深度之旅 30 天系列 第 17

[Day 17] Kotlin Journey - Enum, Sealed Class

Enum

enum 的用法跟在 Java 的時候其實大同小異

舉例來說,我原本有個 Java Enum 如下

這個 enum 有 name 和 code 的內容,還有一個 static 的 getName() 方法

public enum PaymentRtnCode {

    RC2("token 錯誤", "2"),
    RC1("系統維護中", "1"),
    RCN4("無此帳戶", "-4"),
    C0("有未繳費用", "0");

    String name;
    String code;

    PaymentRtnCode(String name, String code) {
        this.name = name;
        this.code = code;
    }

    public static String getName(String code) {
        for (PaymentRtnCode b : PaymentRtnCode.values()) {
            if (b.code.equals(code)) {
                return b.name;
            }
        }
        return "未知的原因";
    }

    public String getName() {
        return name;
    }
    public String getCode() {
        return code;
    }

}

改成 Kotlin 後會像是這樣

是不是簡潔多了呢,注意原本的 name 我換成了 type,因為 Kotlin enum 裡面預設就有一個 name 的屬性

static 方法的部分,就改成了 companion object 的方法

enum class PaymentRtnCode(var type: String, var code: String) {
    RC2("token 錯誤", "2"),
    RC1("系統維護中", "1"),
    RCN4("無此帳戶", "-4"),
    C0("有未繳費用", "0");

    companion object {
        fun getType(code: String): String {
            for (b in values()) {
                if (b.code == code) {
                    return b.type
                }
            }
            return "未知的原因"
        }
    }
}

實際使用的話會像是這樣

fun main() {
    println(PaymentRtnCode.getType(1)) // 系統維護中
}

或是使用 data class 來當作 constructor 參數也可以! 一樣的效果

data class RtnCode(var type: String, var code: String)

enum class PaymentRtnCode(private val rtnCode: RtnCode) {
    RC2(RtnCode("token 錯誤", "2")),
    RC1(RtnCode("系統維護中", "1")),
    RCN4(RtnCode("無此帳戶", "-4")),
    C0(RtnCode("有未繳費用", "0"));

    companion object {
        fun getType(code: String): String {
            for (b in PaymentRtnCode.values()) {
                if (b.code == code) {
                    return b.type
                }
            }
            return "未知的原因"
        }
    }
}

Sealed Class (密封類別)

sealed class 可以說是 enum 的加強版

剛剛在 enum 裡面的種類只能是某一種 class 或固定的參數

像是這樣 RC2("token 錯誤", "2"),

但如果今天我們想要很多種樣式的 enum 時候,就可以使用 sealed class

sealed class 裡面可以包含多個不同的 class 或 object 或 data class,這些物件透過繼承 sealed class 而統一成為一個大家庭!

像下面的例子

sealed class Code {
    data class RCode(val number: Double) : Code()
    data class VCode(val name: String, val code: String) : Code()
    object ZCode : Code()

    companion object {
        fun getCode(code: Code): String {
            return when (code) {
                is RCode -> "RCode type"
                is VCode -> "VCode type"
                is ZCode -> "ZCode type"
            }
        }
    }
}

這三個類別都繼承了 sealed class Code

data class RCode(val number: Double) : Code()
data class VCode(val name: String, val code: String) : Code()
object ZCode : Code()

sealed class 通常都會搭配 when 使用,因為透過繼承 sealed class,所以 when() 的參數就可以使用 sealed class Code

接著在裡面針對子類做判斷

    return when (code) {
        is RCode -> "RCode type"
        is VCode -> "VCode type"
        is ZCode -> "ZCode type"
    }

must be exhaustive(詳盡的)

使用 sealed class 最大的優點是如果在 when 裡面你少寫了某個 case 他會自動幫你檢查!提醒你一定要加上,不然就是要用 else 來對應其他的結果。

https://ithelp.ithome.com.tw/upload/images/20200926/20129902VpLHs5GwTN.png

另外 sealed class 也可以寫成像是這樣,沒有大括號 {},都是分開寫的也可以,因為主要原理是透過繼承實現的。

sealed class Code2

data class RCode(val number: Double) : Code2()
data class VCode(val name: String, val code: String) : Code2()
object ZCode : Code2()

fun getCode(code: Code2): String {
    return when (code) {
        is RCode -> "RCode type"
        is VCode -> "VCode type"
        is ZCode -> "ZCode type"
    }
}

使用一般 class 的話

剛剛講到 sealed class 是透過繼承完成的,那是不是一般 class 也可以做到呢??

是可以做到,但是一般 class 的話,使用 when 就一定要有 else 來應對其他的狀況,所以結論是 sealed class 還是比較適合來使用在需要列舉的狀況下的。

https://ithelp.ithome.com.tw/upload/images/20200926/20129902v3UV7XJI6Q.png

Ktor OAuth

最後我在舉一個最近看到的例子,在 ktor 的 OAuth 取得認證的 principal 的時候,資料也是用 sealed class 來表示。

sealed class OAuthAccessTokenResponse 繼承了 Principal

Principal 通常來說是代表認證的 user 物件

然後裡面定義了兩種不同的 data class OAuth1a, OAuth2 都繼承了 sealed class OAuthAccessTokenResponse

sealed class OAuthAccessTokenResponse : Principal {
    data class OAuth1a(
            val token: String, val tokenSecret: String,
            val extraParameters: Parameters = Parameters.Empty
    ) : OAuthAccessTokenResponse()

    data class OAuth2(
            val accessToken: String, val tokenType: String,
            val expiresIn: Long, val refreshToken: String?,
            val extraParameters: Parameters = Parameters.Empty
    ) : OAuthAccessTokenResponse()
}

所以當取得使用者認證資訊時,會特別針對 principal<OAuthAccessTokenResponse.OAuth2>

這個類型取到物件然後對 principal 內的 accessToken 來做 JWT.decode ,最後取得 token

val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
if (principal != null) {
    val token = JWT.decode(principal.accessToken)
    //todo
} else {
    call.respond(HttpStatusCode.Unauthorized)
}

以上就是今天的介紹!我們明天見!

今日練習的程式在這: 請點我


上一篇
[Day 16] Kotlin Journey - Data Class
下一篇
[Day 18] Kotlin Journey - Array (陣列)
系列文
Kotlin 島深度之旅 30 天31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言