我有代表一棵树的不可变类,其中子代需要父代引用。
sealed class Node {
abstract val parent: Parent?
}
class Child(
override val parent: Parent,
) : Node()
class Parent(
override val parent: Parent?,
val children: List<Node>
) : Node()
是否有一种惯用的Kotlin方法来创建带有包含正确设置了父级引用的子级列表的父级?
答案 0 :(得分:2)
您可以尝试使用根节点和构建器参数:
sealed class Node {
data class Root(
val createChildren: ParentList.() -> Unit
) : Node() {
val children: List<Node> = ParentList(this).apply(createChildren)
}
data class Branch(
val createChildren: ParentList.() -> Unit,
val parent: Node
) : Node() {
val children: List<Node> = ParentList(this).apply(createChildren)
}
data class Leaf(
val parent: Node
) : Node()
}
class ParentList(
val parent: Node,
private val children: MutableList<Node> = mutableListOf()
) : List<Node> by children {
fun branch(createChildren: ParentList.() -> Unit) {
children += Node.Branch(createChildren, parent)
}
fun leaf() {
children += Node.Leaf(parent)
}
}
fun root(createChildren: ParentList.() -> Unit) = Node.Root(createChildren)
它们的结构可以如下所示(可能要向两个节点中的任何一个添加额外的细节):
fun usage() {
val graph = root {
branch {
leaf()
}
branch {
branch {
leaf()
}
leaf()
}
}
}
您可以允许访问具有扩展属性的潜在孩子和/或父母:
val Node.children: List<Node> get() = when(this) {
is Node.Root -> children
is Node.Branch -> children
is Node.Leaf -> emptyList()
}
val Node.parent: Node? get() = when(this) {
is Node.Root -> null
is Node.Branch -> parent
is Node.Leaf -> parent
}
因此您可以导航后代:
fun Node.allChildren(): List<Node> =
children + children.flatMap { it.allChildren() }
或向上导航:
fun Node.allParents(): List<Node> =
listOfNotNull(parent).flatMap { listOf(it) + allParents() }
为避免评估可能会提前完成的搜索,您始终可以使用序列而不是列表:
fun Node.allChildren(): Sequence<Node> =
children.asSequence() + children.asSequence().flatMap { it.allChildren() }
fun Node.allParents(): Sequence<Node> =
listOfNotNull(parent).asSequence().flatMap { sequenceOf(it) + it.allParents() }
注意:当心Stackoverflows
答案 1 :(得分:1)
另一种方法可能是使parent
中的Node
可设置,并为其提供要为其设置父项的子项列表。
sealed class Node(
children: List<Node> = listOf()
) {
var parent: Container? = null
private set
init {
(this as? Container)?.apply {
children.forEach { it.parent = this }
}
}
}
data class Child(val type: String) : Node()
data class Container(
val children: List<Node>
) : Node(children)
唯一的副作用是,parent
不会被数据类用作属性。
答案 2 :(得分:1)
您可能会选择只读,而不是坚持不变性。认为它类似于kotlins MutableList
/ List
。像这样:
sealed class Node {
abstract val parent: Container?
}
abstract class Child : Node()
abstract class Container : Node() {
abstract val children: List<Node>
}
private class MutableChild(override var parent: Container?) : Child()
private class MutableContainer(override var parent: Container?, override var children: List<Node>) : Container()
fun buildTree() : Node {
val children = mutableListOf<Node>()
val parent = MutableContainer(null, children)
MutableChild(parent).also { children.add(it) }
MutableChild(parent).also { children.add(it) }
return parent
}
答案 3 :(得分:0)
我们可以在第一次需要孩子时懒惰地评估建造者的清单。请注意,只有在其余代码库具有足够的参照透明性的情况下,这才是安全的。
sealed class Node {
abstract val parent: Parent?
}
class Child(
override val parent: Parent
) : Node()
class Parent(
override val parent: Parent?,
private val childBuilders: List<(Parent) -> Node>
) : Node() {
val children: List<Node> by lazy {
childBuilders.map { it(this)}
}
}
答案 4 :(得分:0)
我们可以在父对象中填充一个可变的列表,当我们获得引用时填充该列表。
sealed class Node {
abstract val parent: Parent?
}
class Child(
override val parent: Parent
) : Node()
class Parent(
override val parent: Parent?,
val children: List<Node>
) : Node() {
companion object {
operator fun invoke(parent: Parent?, childBuilders: List<(Parent) -> Node>): Parent {
val children = mutableListOf<Node>()
return Parent(parent, children).apply {
children.addAll(childBuilders.map { it(this) })
}
}
}
}