如何在不可变的Kotlin类中创建父子关系

时间:2018-11-03 10:09:15

标签: kotlin

我有代表一棵树的不可变类,其中子代需要父代引用。

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方法来创建带有包含正确设置了父级引用的子级列表的父级?

5 个答案:

答案 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) })
            }
        }
    }
}
相关问题