【问题标题】:How to use type-safe builders with immutable types in Kotlin如何在 Kotlin 中使用具有不可变类型的类型安全构建器
【发布时间】:2018-06-03 13:35:19
【问题描述】:

我想在 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 是您目前能做的最好的选择,即使有必须维护构建器类的负担。此外,您可以在构建结果以引发有意义的异常时添加检查以查看属性是否已初始化。
  • 为了使维护构建器类变得简单,您可以delegate all its fields to a map。仍然不理想,但消除了大部分样板代码。

标签: kotlin immutability builder


【解决方案1】:

这是一种 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 不同,如果您尝试在 builder 块之后更改 p1p2 的值,编译器会告诉您。

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

interface DataClass
{
    companion object
    {
        fun builder(callback : DataClassBuilder.() -> Unit) : DataClass
            = DataClassBuilder().apply { callback() }.let { DataClassImpl(it) }
    }

    val p1 : String
    val p2 : String
}

class DataClassBuilder : DataClass
{
    override lateinit var p1 : String
    override lateinit var p2 : String
}

class DataClassImpl(
    private val delegate : DataClass
) : DataClass by delegate

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多