Я хочу использовать типобезопасные компоновщики в Kotlin для типа, который имеет неизменные свойства.
class DataClass(val p1:String, val p2:String) {}
fun builder(buildBlock: DataClass.() -> Unit) {
return DataClass(p1 = "",p2 = "").also(block)
}
//...
builder {
p1 = "p1" // <-- This won't compile, since p1 is a val
p2 = "p2" // <-- This won't compile, since p2 is a val
}
Я подумал о двух решениях, чтобы обойти это:
Вариант 1: Создать класс построителя:
class DataClass(val p1: String, val p2: String) {}
class DataClassBuilder(){
lateinit var p1: String
lateinit var p2: String
fun build() = DataClass(p1, p2)
}
fun builder(buildBlock: DataClassBuilder.() -> Unit) {
return DataClassBuilder().also(block).build()
}
Вариант 2: Создайте пользовательский делегат для предотвращения повторного задания значения:
class InitOnceDelegate: ReadWriteProperty<DTest, String> {
private var state: String? = null
override fun getValue(thisRef: DTest, property: KProperty<*>): String {
return state ?: throw IllegalStateException()
}
override fun setValue(thisRef: DTest, property: KProperty<*>, value: String) {
if (state == null) {
state = value
} else {
throw IllegalStateException("${property.name} has already been initialized")
}
}
}
class DataClass() {
var p1: String by InitOnceDelegate()
var p2: String by InitOnceDelegate()
}
fun builder(buildBlock: DataClass.() -> Unit) {
return DataClass(p1 = "",p2 = "").also(block)
}
//...
val d = builder {
p1 = "p1"
p2 = "p2"
}
d.p1 = "another value" // <-- This will throw an exception now.
У варианта 1 есть недостаток, заключающийся в том, что я должен поддерживать два класса, у варианта 2 есть недостаток, который разрешает компиляторустановите значение в DataClass
еще раз, и проверка будет выполняться только во время выполнения.
Есть ли лучший способ решить эту проблему без указанных недостатков?