如何在Kotlin中使用具有不可变类型的类型安全构建器

时间:2018-06-03 13:35:20

标签: kotlin immutability builder

我希望在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中的值,并且检查只会在运行时进行。

有没有更好的解决方法,没有上述缺点?

1 个答案:

答案 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}之后更改p1p2的值阻止。

为了避免能够将结果转换为builder并在事后更改值,您可以像这样委托接口:

DataClassImpl