Imagine a Person class with a mutable name where, every time the name is set, some processing happens.
class Person(name: String) {
var name: String = name
set(value) {
field = value
// do some processing
}
}
In the above code, however, the processing will not happen the first time name is set, from the initializer. This is a problem for me, as I need the processing to happen every single time name is set, including during initialization.
What is the best way to achieve this in Kotlin?
I realize I could extract “// do some processing” into a method and call this method from both the setter and an init block, but this seems too wordy. Is there something better?
var name: String by OnChangeDelegate(initalValue) {
// do some proessing
}
class OnChangeDelegate<T>(var state: T, val onChange: (T) -> Unit): ReadWriteProperty {
override operator fun getValue(thisRef: R, property: KProperty<*>): T = state
override operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
state = value
onChange(state)
}
}
Woops. I forgot to add a call to onChange in the constructor of OnChangeDelegate so you would need to add that.
The standard library provides both an observable and vetoable delegate but neither executes the onChange code on initialization. Also I wouldn’t really call this a complex implementation. Delegates are quite common in kotlin (the standard library for example provides variations for lazy, observable and maps can also be used as a delegate).
class Person(name: String) {
var name: String = name
set(value) {
field = value
hash = computeHash(name)
}
private var hash: String = computeHash(name)
}
This computes the hash on initialization and then every time name is changed. I guess delegates wouldn’t work here? Would you use the code like the above in this scenario?
I don’t see any reason why they wouldn’t. You just need to be careful about initialization order or maybe use lateinit
class Person(name: String) {
var name: String by OnChangeDelegate(name) { hash = computeHash(name) }
private lateinit var hash: String
}
It depends. If you only ever want to compute a hash I probably wouldn’t add the delegate for that alone. But if you use this often I can see using a delegate instead. It depends on how often it would be used. If it is only once in a big codebase it is probably more confusing than helpful, but if you can use it often it will improve readability because then everyone knows directly “this code is executed always on change and on initialization”.