改进 DSL 语法

Improving DSL syntax

要开始使用 Kotlin 语言功能学习 DSL 设计,我有 下面尝试使用玩具 DSL 来创建具有成员的成员组 有名字。我正在寻找pointers/hints以下

  1. 如果编译器没有给出分号,我如何避免必须用分号分隔组

Groups.kt:31:45: error: unresolved reference: member val grp = group { member { name ("Bob") } member { name ("Sandy") } }

  1. 我可以使用 lambda 来设置 name 而不是函数调用吗?

  2. 我可以避免 name 在 class MEMBER 中可变吗?

我的密码是

fun group(create: GROUP.() -> Unit) = GROUP().apply(create)

class GROUP {
    private val members = mutableSetOf<MEMBER>()

    fun member(create: MEMBER.() -> Unit) {
        val member = MEMBER()
        member.create()
        members.add(member)
    }

    override fun toString() = members.toString()

}

class MEMBER() {
    var name = ""
    set(value) {
        field = value
    }

    fun name(nameToSet: String) {
        name = nameToSet
    }
    override fun toString() = "MEMBER(" + name + ")"
}

fun main(args: Array<String>) {
    val grp = group { member { name ("Bob") }; member { name ("Sandy") } }
    println(grp)
}

目前上面代码的输出是

[MEMBER(Bob), MEMBER(Sandy)]

How can I avoid having to separate groups by a semicolon

通过使用惯用格式,使用单独的行。毕竟,DSL 的全部意义在于通过显示层次结构使代码更具可读性,而在一行上完成所有事情会破坏整个目的:

val grp = group { 
    member { 
        name ("Bob") 
    }
    member { 
        name ("Sandy") 
    } 
}

Can I get to use a lambda for setting name instead of function call?

删除 name 函数并简单地为 属性:

赋值会更合乎逻辑和惯用
name = "Bob"

但是是的,你也可以用

替换你的名字函数
fun name(block: () -> String) {
    this.name = block()
}

并使用

name {
    "Sandy"
}

Can I avoid having to have name be mutable in class MEMBER?

是:传递给 member() 函数的 lambda 将自定义一个额外的 MemberBuilder class,它是可变的,但允许创建一个不可变的 MEMBER:

fun group(create: GROUP.() -> Unit) = GROUP().apply(create)

class GROUP {
    private val members = mutableSetOf<MEMBER>()

    fun member(configure: MemberBuilder.() -> Unit) {
        val memberBuilder = MemberBuilder()
        memberBuilder.configure()
        members.add(memberBuilder.build())
    }

    override fun toString() = members.toString()

}

class MEMBER(val name: String) {
    override fun toString() = "MEMBER($name)"
}

class MemberBuilder {
    var name = "";

    fun build() = MEMBER(name)
}

fun main(args: Array<String>) {
    val grp = group {
        member {
            name = "Bob"
        }
        member {
            name = "Sandy"
        }
    }
    println(grp)
}

此外,请注意,按照惯例,classes 是 PascalCased,而不是 ALL_CAPS。