如何在自定义 Kotlin DSL 上执行使用规则

Hec*_*tor 10 dsl kotlin

我正在按照以下示例调查 Kotlin DSL:-

https://github.com/zsmb13/VillageDSL

我对如何对 DSL 公开的所有属性实施使用规则感兴趣。

以下面的例子为例:-

val v = village {
    house {
        person {
            name = "Emily"
            age = 31
        }
         person {
            name = "Jane"
            age = 19
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我想强制执行一个规则,阻止 DSL 的用户能够输入重复的属性,如下所示

val v = village {
    house {
        person {
            name = "Emily"
            name = "Elizabeth"
            age = 31
        }
         person {
            name = "Jane"
            age = 19
            age = 56
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试过使用 Kotlin 合同,例如

contract { callsInPlace(block, EXACTLY_ONCE) }
Run Code Online (Sandbox Code Playgroud)

但是,这些只允许在顶级函数中使用,并且在遵循 DSL 中的 Builder 模式时,我看不到如何使用合同,例如

@SimpleDsl1
class PersonBuilder(initialName: String, initialAge: Int) {
    var name: String = initialName
    var age: Int = initialAge

    fun build(): Person {
        return Person(name, age)
    }
}
Run Code Online (Sandbox Code Playgroud)

是否可以达到我想要的每个人只强制设置一个属性的效果?

Lau*_*nce 6

不幸的是,您不能使用合同来获取您正在寻找的编译错误。我不认为它们是为了你在这里绑的目的......但我可能是错的。对我来说,它们是向编译器提示可空性和不变性之类的东西。即使您可以按照自己的意愿使用它们,我也不认为您会遇到您正在寻找的编译错误。

但是第二个解决方案是在运行时有一个异常。属性委托可以为此提供一个很好的可重用解决方案。这是对您的示例进行了一些修改。

class PersonBuilder {
    var name: String? by OnlyOnce(null)
    var age: Int? by OnlyOnce(null)

    fun build(): Person {
        name?.let { name ->
            age?.let { age ->
                return Person(name, age)
            }
        }
        throw Exception("Values not set")
    }
}

class OnlyOnce<V>(initialValue: V) {

    private var internalValue: V = initialValue
    private var set: Boolean = false

    operator fun getValue(thisRef: Any?, property: KProperty<*>): V {
        return internalValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
        if (set) {
            throw Exception("Value set already")
        }
        this.internalValue = value
        this.set = true
    }
}

fun person(body: PersonBuilder.() -> Unit) {
    //do what you want with result
    val builder = PersonBuilder()
    builder.body()
}

fun main() {
    person {
        name = "Emily"
        age = 21
        age = 21 // Exception thrown here
    }
}
Run Code Online (Sandbox Code Playgroud)