web-dev-qa-db-fra.com

Getter privé et setter public pour une propriété Kotlin

Comment faire une propriété à Kotlin qui a un getter privé (ou tout simplement ne pas l'avoir) mais qui a un setter public?

var status
private get

ne fonctionne pas avec une erreur: Getter visibility must be the same as property visibility

Dans mon cas, la raison en est Java interop: je veux que mon code Java puisse appeler setStatus mais pas getStatus.

36

Il est impossible pour le moment à Kotlin d'avoir une propriété avec un setter plus visible que la propriété. Il y a un problème de conception de langue dans le suivi des problèmes à ce sujet, n'hésitez pas à le regarder/voter ou à partager vos cas d'utilisation: https://youtrack.jetbrains.com/issue/KT-311

30
Alexander Udalov

Dans la version actuelle de Kotlin (1.0.3), la seule option est d'avoir une méthode setter séparée comme ceci:

class Test {
    private var name: String = "name"

    fun setName(name: String) {
        this.name = name
    }
}

Si vous souhaitez empêcher les bibliothèques externes d'accéder au getter, vous pouvez utiliser le modificateur de visibilité internal vous permettant de continuer à utiliser la syntaxe des propriétés dans la bibliothèque:

class Test {
    internal var name: String = "name"
    fun setName(name: String) { this.name = name }
}

fun usage(){
    val t = Test()
    t.name = "New"
}
24
miensol

Des propriétés en écriture seule avec des erreurs de compilation peut être obtenues depuis Kotlin 1.0, en utilisant une solution de contournement basée sur @Deprecated.

La mise en oeuvre

Kotlin permet de marquer les fonctions obsolètes avec le niveau ERROR, ce qui conduit à une erreur de compilation lors de l'appel. Annoter l'accessoire get d'une propriété comme obsolète , combiné avec un champ de support (afin que les lectures privées soient toujours possibles), atteint le comportement souhaité:

class WriteOnly {
    private var backing: Int = 0

    var property: Int
        @Deprecated("Property can only be written.", level = DeprecationLevel.ERROR)
        get() = throw NotImplementedError()
        set(value) { backing = value }

    val exposed get() = backing // public API
}

Usage:

val wo = WriteOnly()
wo.property = 20         // write: OK

val i: Int = wo.property // read: compile error
val j: Int = wo.exposed  // read value through other property

L'erreur de compilation est également très utile:

L'utilisation de "getter pour la propriété: Int" est une erreur. La propriété ne peut être écrite.


Cas d'utilisation

  1. Le cas d'utilisation principal est évidemment les API qui permettent d'écrire des propriétés, mais pas de les lire:

    user.password = "secret"
    val pw = user.password // forbidden
    
  2. Un autre scénario est une propriété qui modifie l'état interne, mais n'est pas elle-même stockée en tant que champ. (Pourrait être fait plus élégamment en utilisant un design différent).

    body.thrust_force = velocity
    body.gravity_force = Vector(0, 0, 9.8)
    // only total force accessible, component vectors are lost
    val f = body.forces
    
  3. Ce modèle est également utile pour les DSL du type suivant:

    server {
        port = 80
        Host = "www.example.com"
    }
    

    Dans de tels cas, les valeurs sont simplement utilisées comme paramètres uniques, et le mécanisme d'écriture seule décrit ici peut empêcher la lecture accidentelle d'une propriété (qui pourrait ne pas encore être initialisée).


Limites

Étant donné que cette fonctionnalité n'a pas été conçue pour ce cas d'utilisation, elle comporte certaines limitations:

  • Si vous y accédez à l'aide d'une référence de propriété, l'erreur de compilation se transforme en erreur d'exécution:

    val ref = wo::property
    val x = ref.get() // throws NotImplementedError
    
  • Il en va de même pour la réflexion.

  • Cette fonctionnalité ne peut pas être externalisée dans un délégué, car une méthode obsolète getValue() ne peut pas être utilisée avec by .

1
TheOperator