Sometimes a bidirectional relationship between objects is helpful, but maintaining the consistency manually is verbose and error-prone. This seems to be a good use-case for property delegation. Is the following something for the standard lib?
Bidirectional relationships come in three flavors.
- one-to-one
- one-to-many / many-to-one
- many-to-many
Let’s consider the first first:
class OneToOne<T, V>(
private val prop: V.() -> KMutableProperty0<T?>
) : ReadWriteProperty<T, V?> {
private var ref: V? = null
override fun getValue(thisRef: T, property: KProperty<*>): V? = ref
override fun setValue(thisRef: T, property: KProperty<*>, value: V?) {
if (ref === value) {
return
}
val old = ref
ref = value
if (old !== null) {
old.prop().set(null)
}
if (value !== null) {
value.prop().set(thisRef)
}
}
}
The usage is short and compact:
class A {
var b: B? by OneToOne { ::a }
}
class B {
var a: A? by OneToOne { ::b }
}
And it works like this:
fun main() {
val a1 = A()
val a2 = A()
val b1 = B()
val b2 = B()
a1.b = b1
check(a1.b === b1)
check(b1.a === a1)
a2.b = b2
check(a2.b === b2)
check(b2.a === a2)
a1.b = b2
check(a1.b === b2)
check(b1.a === null)
check(a2.b === null)
check(b2.a === a1)
}
For many-to-one and many-to-many relationships, similar property delegate providers can be written, which are used like this:
class Parent {
val children: MutableList<Child> by ManyToOne { ::parent }
}
class Child {
var parent: Parent? by OneToMany { ::children }
}
class X {
val ys: MutableList<Y> by ManyToMany { ::xs }
}
class Y {
val xs: MutableList<X> by ManyToMany { ::ys }
}