Reputation: 13713
Via Delegates.observable
, Kotlin permits observable properties. I need, however, the ability of adding observers at runtime, as Java's Observable
class does.
What I have now, is the following:
import java.util.*
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
class MyObservable<T> (var v: T): java.util.Observable() {
operator fun getValue(thisRef: Any, prop: KProperty<*>) = v
operator fun setValue(thisRef: Any, prop: KProperty<*>, newValue: T) {
v = newValue
setChanged()
notifyObservers()
}
}
fun <T> addObserver(prop: KProperty0<T>, observerFn: (T) -> Unit) =
(prop.apply{ isAccessible = true }.getDelegate() as MyObservable<T>)
.addObserver(Observer({ o, _ -> observerFn((o as MyObservable<T>).v) }))
class ObservableExample {
var i: Int by MyObservable(3)
}
fun main(args: Array<String>) {
val ex: ObservableExample = ObservableExample();
addObserver(ex::i, { println(it) })
ex.i = 7
ex.i = 9
// prints:
// 7
// 9
}
It works, but it feels like reinventing the wheel.
Isn't there a standard solution for this?
If not, is what I've done correct?
Upvotes: 3
Views: 3859
Reputation: 30686
This is because you starts with a simple example, and can't find the benefits of Kotlin delegated properties.
Kotlin doesn't forcing you to implements any interface to supports delegated properties, yon can using delegated properties in Kotlin just provide getValue & setValue(?) operators. and their visibility even can be private.
Kotlin provided a provideDelegate operator function since 1.1, that let you manage/control how to create a delegate.
The delegate in Kotlin is working in the background, which means it is invisible from the source code point of view, and let the code source treat a delegated properties as a regular properties.
Kotlin delegated properties can easily let you manage java beans without using PropertyEditorSupport in Java, and you don't need to manage the delegate at all in Kotlin, just to notify the changed property only. for example:
val history = mutableMapOf<String, MutableList<Pair<Any?, Any?>>>()
val subject = Subject()
subject.subscribe { event ->
val each = history.getOrPut(event.propertyName) { mutableListOf() }
each.add(event.oldValue to event.newValue)
}
// v--- treat a delegated property as regular property
subject.number = 1
subject.string = "bar"
subject.number = 2
println(history);
// ^--- {"number":[<null,1>,<1,2>], "string": [<null,"bar">]}
Note: the getValue & setValue operator functions private below.
class Subject {
// v--- manage the delegated property internally
var string: String? by this
var number: Int? by this
private val properties by lazy {
mutableMapOf<Any?, Any?>()
}
private val listeners by lazy {
mutableListOf<PropertyChangeListener>()
}
private operator @Suppress("UNCHECKED_CAST")
fun <T : Any?> getValue(self: Any, prop: KProperty<*>): T {
return properties[prop.name] as T
}
private operator
fun <T : Any?> setValue(self: Any,prop: KProperty<*>, newValue: T) {
val event = PropertyChangeEvent(
self,
prop.name,
properties[prop.name],
newValue
)
properties[prop.name] = newValue
listeners.forEach { it.propertyChange(event) }
}
fun subscribe(listener: (event: PropertyChangeEvent) -> Unit) {
subscribe(PropertyChangeListener { listener(it) })
}
fun subscribe(subscriber: PropertyChangeListener) {
listeners.add(subscriber)
}
}
Upvotes: 0
Reputation: 2046
A slightly shorter variant of the same idea:
import kotlin.properties.Delegates
typealias IntObserver = (Int) -> Unit
class ObservableExample {
val prop1Observers = mutableListOf<IntObserver>()
var prop1: Int by Delegates.observable(0) { prop, old, new ->
prop1Observers.forEach { it(new) }
}
}
fun main(args: Array<String>) {
val example = ObservableExample()
example.prop1Observers.add({ println(it) })
example.prop1 = 1
example.prop1 = 2
}
The output is as expected. Probably, it is better to make observers
property private and add a method to add subscribers but I omitted it for the simplicity.
Upvotes: 2