如何在 Kotlin 中扩展枚举?

问题描述 投票:0回答:6

在我的 Kotlin 项目中,我有一个 DefaultError 枚举

enum class DefaultError {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR
}

我想延长它们,以便我有

enum class NfcAndDefaultError : DefaultError {
    //DefaultError inherited plus
    NFC_ERROR
}

和另一个枚举

enum class KameraAndDefaultError : DefaultError {
    //DefaultError inherited plus
    CAM_ERROR
}

现在,我有

enum class NfcDefaultError {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR,
    NFC_ERROR
}

enum class KameraAndDefaultError {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR,,
    CAM_ERROR
}

我敢打赌 Kotlin 有一个很好的方法?

kotlin enums
6个回答
38
投票

不支持枚举继承的原因不仅仅是“继承是邪恶的”。其实有一个很实际的理由:

enum class BaseColor { BLUE, GREEN, RED }

val x: BaseColor = ... // must be one of the 3 enums, right?
// e.g. when {} can be done exhaustively with BLUE, GREEN, RED

enum class DerivedColor : BaseColor { YELLOW }

val y: BaseColor = ... // now, it can also be YELLOW
// here, you lose the guarantee that it's a value in a limited set
// and thus the main advantage of enums

有多种选择可以实现您喜欢的目标:

1。不同的枚举实现一个通用的接口

我建议您参考这个答案

接口是一种非常灵活的解决方案,允许您派生无限数量的枚举。如果这就是您所需要的,那就去吧。

2。 密封课程

在 Kotlin 中,密封类是枚举的泛化,它允许您保留每个值的状态。密封类的所有派生类必须预先已知并在同一文件中声明。与接口相比的优点是您可以将密封类限制为一组固定的可能类型。例如,这允许您省略

else
中的
when
分支。缺点是无法按照设计在
sealed class
之外添加类型。

从语义上讲,您有一个枚举的枚举:第一级确定使用哪种

enum class
类型,第二级确定使用
enum class
内的哪个枚举器(常量)。

enum class DefaultError { INTERNET_ERROR, BLUETOOTH_ERROR, TEMPERATURE_ERROR }
enum class NfcError { NFC_ERROR }
enum class CameraError { CAM_ERROR }

sealed class Error {
    data class Default(val error: DefaultError) : Error()
    data class Nfc(val error: NfcError) : Error()
    data class Camera(val error: CameraError) : Error()
}

fun test() {
    // Note: you can use Error as the abstract base type
    val e: Error = Error.Default(DefaultError.BLUETOOTH_ERROR)

    val str: String = when (e) {
        is Error.Default -> e.error.toString()
        is Error.Nfc -> e.error.toString()
        is Error.Camera -> e.error.toString()
        // no else!
    }
}

32
投票

您可以扩展枚举。有点儿。但不是继承。枚举可以实现接口。这意味着要扩展它,您只需添加另一个实现相同接口的枚举即可。

假设您有一个错误。该错误有一个错误代码。默认错误被实现为 DefaultError 枚举,并且可以通过添加实现 Error 接口的附加枚举来扩展。

interface Error {
    fun code(): Int
}

enum class DefaultError(private val code: Int) : Error {
    INTERNET_ERROR(1001),
    BLUETOOTH_ERROR(1002),
    TEMPERATURE_ERROR(1003);

    override fun code(): Int {
        return this.code
    }
}

enum class NfcError(private val code: Int) : Error {
    NFC_ERROR(2001);

    override fun code(): Int {
        return this.code
    }
}

enum class KameraError(private val code: Int) : Error {
    CAM_ERROR(3001);

    override fun code(): Int {
        return this.code
    }
}

1
投票

简单的答案是,您无法按照您想要的方式在 Kotlin 中扩展枚举。

我必须同意 Miha_x64 的评论,指出继承是“邪恶的”,只有在合理的情况下才应该使用(是的,在某些情况下仍然需要继承)。我相信,我们为什么不以不同的方式设计我们的解决方案,而不是实际尝试解决 Kotlin 中的枚举设计呢?我的意思是:为什么你认为你需要这样一个枚举层次结构?有什么好处?为什么不简单地设置一些“常见错误”,而针对需要非常具体错误的具体区域设置特定错误呢?为什么还要使用枚举?

如果您执意要使用枚举,那么 Januson 的解决方案可能是您最好的选择,但请使用有意义的错误代码,因为使用“1001”、“1002”、“233245”是如此 1980 年代,阅读和使用起来非常可怕。我发现“INTERNET_ERROR”和“BLUETOOTH_ERROR”等同样神秘......我们真的不能做得更好并且更具体地说明出了什么问题,以便任何读取错误代码的人都可以真正了解问题所在而无需挖掘在接下来的几分钟/几小时内通过互联网或通过一些大量的文档? (当然,如果有一些合理的原因导致代码需要尽可能小,例如消息大小限制、带宽限制等)

如果您不热衷于使用枚举,那么您可以考虑以下内容:

data class ErrorCode(
    val code: String,
    val localeKey: String,
    val defaultMessageTemplate: String
)
val TENANT_ACCESS_FORBIDDEN = ErrorCode(
    "TENANT_ACCESS_FORBIDDEN",
    "CommonErrorCodes.TENANT_ACCESS_FORBIDDEN",
    "Not enough permissions to access tenant ''{0}''."
)
val NO_INTERNET_CONNETION = ErrorCode(
    "NO_INTERNET_CONNETION",
    "DeviceErrorCodes.NO_INTERNET_CONNETION",
    "No internet connection."
)
val NO_BLUETOOTH_CONNECTION = ErrorCode(
    "NO_BLUETOOTH_CONNECTION",
    "DeviceErrorCodes.NO_BLUETOOTH_CONNECTION",
    "No bluetooth connection."
)
val TEMPERATURE_THRESHOLD_EXCEEDED = ErrorCode(
    "TEMPERATURE_THRESHOLD_EXCEEDED",
    "DeviceErrorCodes.TEMPERATURE_THRESHOLD_EXCEEDED",
    "Temperature ''{0}'' exceeds the maximum threshold value of ''{1}''."
)

因为上述所有代码本质上都充当静态常量,因此与它们进行比较就像比较枚举一样简单(例如:

if (yourException.errorCode == NO_INTERNET_CONNECTION) { // do something }
)。

继承其实并不需要,你真正需要的是常见错误代码和非常见错误代码之间的明确区分。


1
投票

从 Kotlin 1.5 开始你就可以使用密封接口来实现你想要的:)

您可以在这里找到一些示例: https://quickbirdstudios.com/blog/sealed-interfaces-kotlin/

如果您需要更多解释,请联系我!


0
投票

目前还没有简单的方法。

使用单独的枚举并使用标记界面键入它

interface Error        // marker interface

enum class DefaultError: Error {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR
}

enum class KameraAndDefaultError: Error {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR,
    CAM_ERROR
}

enum class NfcDefaultError: Error {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR,
    NFC_ERROR
}

用途:

val error: Error = KameraAndDefaultError.INTERNET_ERROR

0
投票

没有继承、密封类或接口的简单解决方案

enum class Fruit(val emoji: String) {
    APPLE("🍎"),
    ORANGE("🍊");
}

enum class ExtendedFruit(emoji: String) {
    APPLE(Fruit.APPLE.emoji),
    ORANGE(Fruit.ORANGE.emoji),
    BANANA("🍌");
}
© www.soinside.com 2019 - 2024. All rights reserved.