7
votes

Comment étendre les énumérations dans Kotlin?

Dans mon projet Kotlin, j'ai une énumération DefaultError

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

Je voudrais les prolonger pour avoir

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

et une autre énumération

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

Maintenant j'ai

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

et

enum class DefaultError {
    INTERNET_ERROR,
    BLUETOOTH_ERROR,
    TEMPERATURE_ERROR
}

Je parie que Kotlin a un bon chemin?


2 commentaires

Bien sûr que non. Les énumérations sont définitives, l'héritage de classe est mauvais.


Regardez également une discussion en Java: stackoverflow.com/questions/35650045/… .


3 Réponses :


6
votes

Vous pouvez étendre un Enum. En quelque sorte. Mais pas avec l'héritage. Les énumérations peuvent implémenter une interface . Cela signifie que pour l'étendre, vous ajouteriez simplement une autre énumération implémentant la même interface.

Disons que vous avez une erreur. Cette erreur a un code d'erreur. Les erreurs par défaut sont implémentées en tant qu'énumération DefaultError et peuvent être étendues en ajoutant des énumérations supplémentaires implémentant l'interface 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
    }
}


2 commentaires

NfcError a-t-il également INTERNET_ERROR, BLUETOOTH_ERROR & TEMP_ERROR?


Non. Pour le consommateur, cela ressemblera aux valeurs d'une seule énumération, mais le fournisseur doit accéder aux valeurs via leurs énumérations spécifiques.



1
votes

La réponse simple est que vous ne pouvez pas étendre les énumérations dans Kotlin comme vous le souhaitez.

Je suis d'accord avec le commentaire de Miha_x64 affirmant que l'héritage est «mauvais» et ne devrait être utilisé que lorsqu'il a légitimement un sens (oui, il y a encore des situations où l'héritage est la voie à suivre). Je pense qu'au lieu d'essayer de contourner la conception des énumérations dans Kotlin, pourquoi ne pas concevoir notre solution différemment? Je veux dire: pourquoi pensez-vous avoir besoin d'une telle hiérarchie d'énumérations, pour commencer? Quel est l'avantage? Pourquoi ne pas avoir simplement quelques "erreurs courantes" à la place et des erreurs spécifiques pour n'importe quel domaine concret qui nécessite des erreurs très spécifiques? Pourquoi même utiliser des énumérations?

Si vous êtes déterminé à utiliser des énumérations, alors la solution de Januson pourrait être votre meilleur pari, mais veuillez utiliser des codes d'erreur significatifs, car l'utilisation de "1001", "1002", "233245" est tellement des années 1980 et absolument horrible à lire et à utiliser. «INTERNET_ERROR» et «BLUETOOTH_ERROR», etc. via Internet ou grâce à une documentation volumineuse pour les prochaines minutes / heures? (sauf, bien sûr, s'il existe des raisons légitimes pour lesquelles le code doit être aussi petit que possible - par exemple, des limitations de taille de message, des limitations de bande passante, etc.)

Si vous n'êtes pas prêt à utiliser des énumérations, vous pouvez envisager les éléments suivants:

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}''."
)
val TENANT_ACCESS_FORBIDDEN = ErrorCode(
    "TENANT_ACCESS_FORBIDDEN",
    "CommonErrorCodes.TENANT_ACCESS_FORBIDDEN",
    "Not enough permissions to access tenant ''{0}''."
)
data class ErrorCode(
    val code: String,
    val localeKey: String,
    val defaultMessageTemplate: String
)

Étant donné que tous les codes ci-dessus, par essence, agissent comme des constantes statiques, les comparer est aussi simple que de comparer des énumérations (par exemple: if (yourException.errorCode == NO_INTERNET_CONNECTION) { // do something } ).

L'héritage n'est vraiment pas nécessaire, ce dont vous avez vraiment besoin est une séparation claire entre les codes d'erreur communs et non courants.


0 commentaires

11
votes

Il y a plus à la raison pour laquelle l'héritage enum n'est pas pris en charge que "l'héritage est mauvais". En fait, une raison très pratique:

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!
    }
}

Il existe plusieurs options pour réaliser ce que vous aimez:

1. Différentes énumérations implémentent une interface commune

Je vous renvoie à cette réponse .

Les interfaces sont une solution très flexible et vous permettent de dériver un nombre illimité d'énumérations. Si c'est ce dont vous avez besoin, allez-y.

2. Classes scellées

Dans Kotlin, les classes scellées sont une généralisation des énumérations, qui vous permet de conserver l'état dans chaque valeur. Toutes les classes dérivées d'une classe scellée doivent être connues à l'avance et déclarées dans le même fichier. L'avantage par rapport aux interfaces est que vous pouvez limiter la classe scellée à un ensemble fixe de types possibles. Cela vous permet d'omettre la branche else dans when , par exemple. L'inconvénient est qu'il n'est pas possible d'ajouter des types en dehors de la sealed class par conception.

Sémantiquement, vous avez une énumération d'énumérations: le premier niveau détermine quel type de enum class est utilisé, et le deuxième niveau détermine quel énumérateur (constante) à l'intérieur de cette enum class est utilisé.

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 commentaires

Merci! Les classes scellées ne peuvent pas être étendues dans d'autres fichiers, leur avantage est donc faible.