Kotlin DSL支持很棒,但是我遇到了两种情况,我只能添加解决方法。两种解决方法都有其主要缺点,因为它们仅在执行时强制执行约束。
我想写这样的东西:
start {
position {
random {
rect(49, 46, 49, 47)
rect(50, 47, 51, 48)
point(51, 49)
}
}
}
其中position是必需的参数。我的方法是在启动时将位置设置为null,并在构建起始对象时对其进行检查。
我想精确地允许以下几个子对象之一:
start {
position {
random {
[parameters of random assign]
}
}
}
or
start {
position {
user {
[parameters of user assign]
}
}
}
我觉得我已经达到了Kotlin DSL工具包的极限,因为此要求也仅在核心语言中经过了编译时验证。
有什么主意吗?
答案 0 :(得分:0)
您可以从Kotlin自己的HTML DSL中获得启发。对于强制性参数,请使用带有参数的简单函数,而不是带有接收器的函数文字。
您的DSL如下所示:
start(
position {// This is mandatory
random {// This is not
}
}
)
还有您的start
构建器:
fun start(position: Position): Start {
val start = Start(position)
...
return start
}
对position()
使用相同的方法。
答案 1 :(得分:0)
考虑了问题后,我意识到,这两个要求无法在Kotlin本身中解决,因此,在上面介绍的当前形式中,不可能有任何纯粹的句法解决方案。但是,有一些选项可能会产生足够接近的语法并同时解决一个或两个问题。
此解决方案非常简单且难看,添加了可怕的“ where-is-the-thes-thes-closing-括号”异常。它只是将position属性移动到构造函数中:
start(random {
rect(49, 46, 49, 47)
rect(50, 47, 51, 48)
point(51, 49)
}) {
windDirection to NORTH
boat turn (BEAM_REACH at STARBOARD)
}
这在代码中很简单:
fun start(pos : StartPosition, op: StartConfigBuilder.() -> Unit) : StartConfigBuilder
= StartConfigBuilder(pos).apply(op)
并为排名实现创建顶级构建器功能:
fun random( op : RandomStartPositionBuilder.() -> Unit) = RandomStartPositionBuilder().apply(op).build()
class RandomStartPositionBuilder {
private val startZoneAreas = mutableListOf<Area>()
fun rect(startRow: Int, startColumn: Int, endRow: Int = startRow, endColumn: Int) =
startZoneAreas.add(Area(startRow, startColumn, endRow, endColumn))
fun point(row: Int, column: Int) = startZoneAreas.add(Area(row, column))
fun build() = RandomStartPosition(if (startZoneAreas.isEmpty()) null else Zone(startZoneAreas))
}
fun user( op : UserStartPositionBuilder.() -> Unit) = UserStartPositionBuilder().apply(op).build()
class UserStartPositionBuilder {
fun build() = UserStartPosition()
}
尽管这解决了编辑时所需的问题,但仅解决了一个问题,但使DSL更加难以阅读,并且我们失去了DSL工具的优雅。如果必须将多个属性移到构造函数中,或者内部对象(位置)变得更加复杂,则会变得更加混乱。
此解决方案将所需的复杂字段移到块外(这是“讨厌的”部分)并将其用作中缀函数:
start {
windDirection to NORTH
boat turn (BEAM_REACH at STARBOARD)
} position random {
rect(49, 46, 49, 47)
rect(50, 47, 51, 48)
point(51, 49)
}
or
start {
windDirection to NORTH
boat turn (BEAM_REACH at STARBOARD)
} position user {
}
此解决方案解决了“仅一个”问题,而不是“完全一个”问题。
为此,我修改了构建器:
//Note, that the return value is the builder: at the end, we should call build() later progmatically
fun start(op: StartConfigBuilder.() -> Unit) : StartConfigBuilder = StartConfigBuilder().apply(op)
class StartConfigBuilder {
private var position: StartPosition = DEFAULT_START_POSITION
private var windDirectionVal: InitialWindDirection = RandomInitialWindDirection()
val windDirection = InitialWindDirectionBuilder()
val boat = InitialHeadingBuilder()
infix fun position(pos : StartPosition) : StartConfigBuilder {
position = pos
return this
}
fun build() = StartConfig(position, windDirection.value, boat.get())
}
// I have to move the factory function top level
fun random( op : RandomStartPositionBuilder.() -> Unit) = RandomStartPositionBuilder().apply(op).build()
class RandomStartPositionBuilder {
private val startZoneAreas = mutableListOf<Area>()
fun rect(startRow: Int, startColumn: Int, endRow: Int = startRow, endColumn: Int) =
startZoneAreas.add(Area(startRow, startColumn, endRow, endColumn))
fun point(row: Int, column: Int) = startZoneAreas.add(Area(row, column))
fun build() = RandomStartPosition(if (startZoneAreas.isEmpty()) null else Zone(startZoneAreas))
}
// Another implementation
fun user( op : UserStartPositionBuilder.() -> Unit) = UserStartPositionBuilder().apply(op).build()
class UserStartPositionBuilder {
fun build() = UserStartPosition()
}
这几乎以一种优雅的方式解决了“仅一个”实现的问题,但是没有给出“ required property”选项的答案。因此,当可以应用默认值时很好,但是在缺少位置时仍然只提供解析时间异常。
此解决方案是先前解决方案的变体。为了解决前一个问题,我们使用一个变量和一个中间类:
var start : StartWithPos? = null
class StartWithoutPos {
val windDirection = InitialWindDirectionBuilder()
val boat = InitialHeadingBuilder()
}
class StartWithPos(val startWithoutPos: StartWithoutPos, pos: StartPosition) {
}
fun start( op: StartWithoutPos.() -> Unit): StartWithoutPos {
val res = StartWithoutPos().apply(op)
return res
}
infix fun StartWithoutPos.position( pos: StartPosition): StartWithPos {
return StartWithPos(this, pos)
}
然后我们可以在DSL中编写以下语句:
start = start {
windDirection to NORTH
boat heading NORTH
} position random {
}
这可以解决两个问题,但要付出额外的变量分配费用。
所有这三种解决方案都有效,给DSL带来了一些麻烦,但可能会选择一种更合适的方法。