我希望在Kotlin中使用type-safe builders作为具有不可变属性的类型。
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
中的值,并且检查只会在运行时进行。
有没有更好的解决方法,没有上述缺点?
答案 0 :(得分:0)
这是一种hacky解决方案,仍然需要你维护两个类:
interface DataClass
{
companion object
{
fun builder(callback : DataClassImpl.() -> Unit) : DataClass
= DataClassImpl().apply { callback() }
}
val p1 : String
val p2 : String
}
class DataClassImpl : DataClass
{
override lateinit var p1 : String
override lateinit var p2 : String
}
与选项1不同,您只在此示例中创建一个实例,与选项2不同,编译器会告诉您是否尝试在{{1}之后更改p1
或p2
的值阻止。
为了避免能够将结果转换为builder
并在事后更改值,您可以像这样委托接口:
DataClassImpl