Поразмыслив над проблемой, я понял, что эти два требования не могут быть решены в самом Котлине, поэтому чисто синтаксическое решение в представленной выше текущей форме невозможно. Однако есть несколько вариантов, которые могут создавать достаточно близкий синтаксис и решать одну или обе проблемы одновременно.
Вариант 1: Параметры
Это решение довольно простое и уродливое, добавляя ужасную аномалию «где-закрывающая скобка». Он просто перемещает свойство 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. Это станет еще более запутанным, если в конструктор нужно будет переместить более одного свойства или если внутренний объект (позиция) станет более сложным.
Вариант 2: Инфиксная функция
Это решение перемещает требуемое сложное поле за пределы блока (это «неприятная» часть) и использует его как инфиксную функцию:
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()
}
Это решает проблему «только одной» реализации почти элегантным способом, но не дает ответа на параметр «требуемое свойство». Так что это хорошо, когда можно применить значение по умолчанию, но все же дает только исключение времени анализа, когда позиция отсутствует.
Вариант 3: Цепочка инфиксных функций
Это решение является вариантом предыдущего. Чтобы решить требуемую проблему предыдущего, мы используем переменную и промежуточный класс:
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, но можно выбрать то, что подходит лучше.
person
Balage1551
schedule
06.01.2019
position<User> { }
иposition<Random> { }
? - person Jonas Wilms   schedule 06.01.2019