De punere în aplicare proprietăți observabile, care pot, de asemenea, serialize în Kotlin

0

Problema

Eu sunt încercarea de a construi o clasă în care anumite valori sunt Observabile, dar, de asemenea, Serializable.

Evident, acest lucru nu funcționează și serialization funcționează, dar este foarte șabloane-grele a fi nevoie să adăugați un setter pentru fiecare domeniu și manual a avea pentru a apela change(...) în interiorul fiecare setter:

interface Observable {

    fun change(message: String) {
        println("changing $message")
    }
}

@Serializable
class BlahVO : Observable {

    var value2: String = ""
        set(value) {
            field = value
            change("value2")
        }

    fun toJson(): String {
        return Json.encodeToString(serializer(), this)
    }
}

println(BlahVO().apply { value2 = "test2" }) corect ieșiri

changing value2
{"value2":"test2"}

Am încercat introducerea Delegați:

interface Observable {

    fun change(message: String) {
        println("changing $message")
    }

    
    @Suppress("ClassName")
    class default<T>(defaultValue: T) {

        private var value: T = defaultValue

        operator fun getValue(observable: Observable, property: KProperty<*>): T {
            return value
        }

        operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
            this.value = value
            observable.change(property.name)
        }

    }

}

@Serializable
class BlahVO : Observable {

    var value1: String by Observable.default("value1")

    fun toJson(): String {
        return Json.encodeToString(serializer(), this)
    }
}

println(BlahVO().apply { value1 = "test1" }) corect declanșează detectare schimbare, dar nu serialize:

changing value1
{}

Dacă mă duc la Observabile la ReadWriteProperty,

interface Observable {

    fun change(message: String) {
        println("changing $message")
    }

    fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
        return OP(defaultValue, this)
    }

    class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            super.setValue(thisRef, property, value)
            observable.change("blah!")
        }
    }
}

@Serializable
class BlahVO : Observable {

    var value3: String by this.look("value3")

    fun toJson(): String {
        return Json.encodeToString(serializer(), this)
    }
}

rezultatul este același:

changing blah!
{}

În mod similar pentru Delegați.vetoable

var value4: String by Delegates.vetoable("value4", {
        property: KProperty<*>, oldstring: String, newString: String ->
    this.change(property.name)
    true
})

ieșiri:

changing value4
{}

Delegații nu pare să lucreze cu Kotlin Serialization

Ce alte opțiuni sunt acolo pentru a observa o proprietate schimbări fără ruperea acestuia serialization care va lucra, de asemenea, pe alte platforme (KotlinJS, KotlinJVM, Android, ...)?

1

Cel mai bun răspuns

2

Serializarea și Deserializarea Kotlin Delegați nu este susținută de kotlinx.serialization ca de acum.
Există o problemă deschisă #1578 pe GitHub cu privire la această caracteristică.

În funcție de problema puteți crea un intermediar de transfer de date obiect, care devine serializat în loc de obiectul original. De asemenea, ai putea scrie un obicei serializer pentru a sprijini serialization Kotlin Delegați, care pare a fi chiar și mai multe șabloane, apoi de scris personalizate getters și setteri, așa cum a propus în întrebare.


Transferul De Date Obiect

De cartografiere obiectul original la un simplu transfer de date obiect fără delegați, puteți utiliza implicit de serializare mecanisme. Acest lucru are, de asemenea, efect secundar frumos pentru a curăța model de date clasele din framework specifice adnotări, cum ar fi @Serializable.

class DataModel {
    var observedProperty: String by Delegates.observable("initial") { property, before, after ->
        println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
    }

    fun toJson(): String {
        return Json.encodeToString(serializer(), this.toDto())
    }
}

fun DataModel.toDto() = DataTransferObject(observedProperty)

@Serializable
class DataTransferObject(val observedProperty: String)

fun main() {
    val data = DataModel()
    println(data.toJson())
    data.observedProperty = "changed"
    println(data.toJson())
}

Acest lucru dă următorul rezultat:

{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}

Tip de date personalizate

Dacă se schimbă tipul de date este o opțiune, ai putea scrie un ambalaj de clasă care se (de)serializat în mod transparent. Ceva de-a lungul liniilor dintre următoarele ar putea să funcționeze.

@Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
    fun toJson(): String {
        return Json.encodeToString(serializer(), this)
    }
}

fun main() {
    val monitoredString = obs("obsDefault") { before, after ->
        println("""I changed from "$before" to "$after"!""")
    }
    
    val data = ClassWithMonitoredString(monitoredString)
    println(data.toJson())
    data.monitoredProperty.value = "obsChanged"
    println(data.toJson())
}

Care dă următorul rezultat:

{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}

Cu toate acestea, pierde informații despre ce proprietate s-a schimbat, ca nu au acces ușor la numele câmpului. De asemenea, trebuie să-ți schimbi structuri de date, după cum sa menționat mai sus, și ar putea să nu fie de dorit sau chiar posibil. În plus, acest lucru doar pentru Siruri de caractere pentru acum, chiar dacă s-ar putea face mai mult generic, deși. De asemenea, acest lucru necesită o mulțime de șabloane pentru a începe cu. La apelul site cu toate acestea, trebuie doar să-și încheie valoarea reală într-un apel la obs. Am folosit următoarele șabloane pentru a face să funcționeze.

typealias OnChange = (before: String, after: String) -> Unit

@Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
    var value: String = initialValue
        set(value) {
            onChange?.invoke(field, value)

            field = value
        }

}

fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)

object MonitoredStringSerializer : KSerializer<MonitoredString> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: MonitoredString) {
        encoder.encodeString(value.value)
    }

    override fun deserialize(decoder: Decoder): MonitoredString {
        return MonitoredString(decoder.decodeString(), null)
    }
}
2021-11-24 18:19:41

Am în prezent, urmează o abordare similară, dar se simte ca ar putea fi mai bine. Am mers un pas mai departe, creând o metodă monitoredString care returnează o MonitoredString și din funcția are acces la aceasta, nu trebuie să treacă onChange, pot doar link-ul de la OnChange de acest lucru. Dezavantajul de a avea un Observabile "de stat" de clasă și apoi un transfer de date de clasă, care pot fi serializate este duplicarea modelului domenii. Se pare că singura soluție bună, care realizează ceea ce vreau să fac, este de a adnota cu @Ceva și apoi genera șabloane folosind KSP.
Jan Vladimir Mostert

În alte limbi

Această pagină este în alte limbi

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................