Kotlin'de de seri hale getirilebilen gözlemlenebilir özelliklerin uygulanması

0

Soru

Belirli değerlerin Gözlemlenebilir olduğu ancak aynı zamanda Seri Hale Getirilebileceği bir sınıf oluşturmaya çalışıyorum.

Bu açıkça işe yarıyor ve serileştirme işe yarıyor, ancak her bir alan için bir ayarlayıcı eklemek ve manuel olarak aramak zorunda kalmak çok zor change(...) her ayarlayıcı içinde:

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" }) doğru çıkışlar

changing value2
{"value2":"test2"}

Delegeleri tanıtmayı denedim.:

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" }) değişiklik algılamayı doğru şekilde tetikler, ancak seri hale getirmez:

changing value1
{}

Gözlemlenebilir'den readwriteproperty'ye gidersem,

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

sonuç aynı:

changing blah!
{}

Delegeler için de aynı şekilde.veto edilebilir

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

çıktılar:

changing value4
{}

Delegeler sadece Kotlin Serileştirme ile çalışmıyor gibi görünüyor

Diğer platformlarda da çalışacak olan serileştirmesini bozmadan bir mülkün değişikliklerini gözlemlemek için başka hangi seçenekler vardır (KotlinJS, KotlinJVM, Android,...)?

1

En iyi cevabı

2

Kotlin Delegelerinin seri hale getirilmesi ve Seri dışı bırakılması şu şekilde desteklenmez: kotlinx.serialization şimdi olduğu gibi.
GitHub'da bu özellik ile ilgili açık bir sorun #1578 var.

Soruna göre, orijinal nesne yerine seri hale getirilen bir ara veri aktarım nesnesi oluşturabilirsiniz. Ayrıca, Kotlin Delegelerinin seri hale getirilmesini desteklemek için özel bir seri hale getirici yazabilirsiniz; bu, daha da fazla şablon gibi görünüyor, daha sonra soruda önerildiği gibi özel alıcılar ve belirleyiciler yazıyor.


Veri Aktarım Nesnesi

Özgün nesnenizi temsilciler olmadan basit bir veri aktarım nesnesine eşleyerek varsayılan seri hale getirme mekanizmalarını kullanabilirsiniz. Bu, veri modeli sınıflarınızı aşağıdaki gibi çerçeveye özgü ek açıklamalardan temizlemek için de güzel bir yan etkiye sahiptir @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())
}

Bu, aşağıdaki sonucu verir:

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

Özel veri türü

Veri türünü değiştirmek bir seçenekse, saydam olarak seri hale getirilen (de)bir kaydırma sınıfı yazabilirsiniz. Aşağıdaki satırlar boyunca bir şey işe yarayabilir.

@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())
}

Bu, aşağıdaki sonucu verir:

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

Ancak alan adına kolay erişiminiz olmadığından hangi özelliğin değiştiği hakkında bilgi kaybedersiniz. Ayrıca, yukarıda belirtildiği gibi veri yapılarınızı değiştirmeniz gerekir ve istenmeyebilir veya hatta mümkün olmayabilir. Buna ek olarak, bu sadece şu an için Dizeler için çalışıyor olsa da, daha genel hale getirilebilir. Ayrıca, bu başlamak için çok fazla kazan plakası gerektirir. Ancak, arama sitesinde, gerçek değeri şu adrese yapılan bir aramaya kaydırmanız yeterlidir: obs. Çalışması için aşağıdaki plakayı kullandım.

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

Şu anda benzer bir yaklaşım izliyorum, ancak daha iyi olabileceğini hissediyorum. Bir MonitoredString döndüren bir yöntem monitoredString oluşturmak için bir adım daha ileri gittim ve işlev buna erişebildiğinden, Onchange'i geçmek zorunda değilim, sadece bundan Onchange'e bağlayabilirim. Gözlemlenebilir bir "durum" sınıfına ve ardından seri hale getirilebilen bir veri aktarım sınıfına sahip olmanın dezavantajı, model alanlarının çoğaltılmasıdır. Yapmak istediğim şeyi başaran tek iyi çözüm, @Something ile açıklama yapmak ve daha sonra KSP kullanarak boilerplate'i oluşturmaktır.
Jan Vladimir Mostert

Diğer dillerde

Bu sayfa diğer dillerde

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