gpt4 book ai didi

generics - 如何强制客户端代码使用合约初始化 Kotlin 中所有必需的构建器字段?

转载 作者:行者123 更新时间:2023-12-04 12:56:41 25 4
gpt4 key购买 nike

在 JetBrains 2019 年开放日上,据说 Kotlin 团队研究了合约并尝试实现上下文合约,它只允许在某些上下文中调用函数,例如函数 build仅当 setName 时才允许调用方法在它之前被调用了一次。 Here是谈话录音。

我尝试使用当前可用的 Kotlin 功能来模拟此类契约(Contract),以创建用于 data class Person(val name: String, val age: Int) 的空安全构建器。 .

注意:当然,在这种情况下,使用命名参数而不是构建器模式要容易得多,但是命名参数不允许将未完全构建的对象解析为其他函数,并且在您想要创建时很难使用它们由其他复杂对象等组成的复杂对象。

所以这是我的空安全构建器实现:

基于通用标志的构建器

sealed class Flag {
object ON : Flag()
object OFF : Flag()
}

class PersonBuilder<NAME : Flag, AGE : Flag> private constructor() {
var _name: String? = null
var _age: Int? = null

companion object {
operator fun invoke() = PersonBuilder<OFF, OFF>()
}
}

val PersonBuilder<ON, *>.name get() = _name!!
val PersonBuilder<*, ON>.age get() = _age!!

fun <AGE : Flag> PersonBuilder<OFF, AGE>.name(name: String): PersonBuilder<ON, AGE> {
_name = name
@Suppress("UNCHECKED_CAST")
return this as PersonBuilder<ON, AGE>
}

fun <NAME : Flag> PersonBuilder<NAME, OFF>.age(age: Int): PersonBuilder<NAME, ON> {
_age = age
@Suppress("UNCHECKED_CAST")
return this as PersonBuilder<NAME, ON>
}

fun PersonBuilder<ON, ON>.build() = Person(name, age)

优点:
  • 一个人只有在nameage被指定。
  • 无法重新分配属性。
  • 部分构建的对象可以安全地保存到变量中并传递给函数。
  • 函数可以指定构建器的所需状态和将返回的状态。
  • 属性可以在赋值后使用。
  • 流畅的界面。

  • 缺点:
  • 此构建器不能与 DSL 一起使用。
  • 如果不添加类型参数并破坏所有现有代码,则无法添加新属性。
  • 每次都必须指定所有泛型(即使函数不关心 age ,它也必须声明它接受具有任何 AGE 类型参数的构建器并返回具有相同类型参数的构建器。)
  • _name_age属性不能是私有(private)的,因为它们应该可以从扩展函数中访问。

  • 这是此构建器使用示例:
    PersonBuilder().name("Bob").age(21).build()
    PersonBuilder().age(21).name("Bob").build()
    PersonBuilder().name("Bob").name("Ann") // doesn't compile
    PersonBuilder().age(21).age(21) // doesn't compile
    PersonBuilder().name("Bob").build() // doesn't compile
    PersonBuilder().age(21).build() // doesn't compile

    val newbornBuilder = PersonBuilder().newborn() // builder with age but without name
    newbornBuilder.build() // doesn't compile
    newbornBuilder.age(21) // doesn't compile
    val age = newbornBuilder.age
    val name = newbornBuilder.name // doesn't compile
    val bob = newbornBuilder.name("Bob").build()
    val person2019 = newbornBuilder.nameByAge().build()
    PersonBuilder().nameByAge().age(21).build() // doesn't compile

    fun PersonBuilder<OFF, ON>.nameByAge() = name("Person #${Year.now().value - age}")
    fun <NAME : Flag> PersonBuilder<NAME, OFF>.newborn() = age(0)

    基于契约(Contract)的 build 者
    sealed class PersonBuilder {
    var _name: String? = null
    var _age: Int? = null

    interface Named
    interface Aged

    private class Impl : PersonBuilder(), Named, Aged

    companion object {
    operator fun invoke(): PersonBuilder = Impl()
    }
    }

    val <S> S.name where S : PersonBuilder, S : Named get() = _name!!
    val <S> S.age where S : PersonBuilder, S : Aged get() = _age!!

    fun PersonBuilder.name(name: String) {
    contract {
    returns() implies (this@name is Named)
    }
    _name = name
    }

    fun PersonBuilder.age(age: Int) {
    contract {
    returns() implies (this@age is Aged)
    }
    _age = age
    }

    fun <S> S.build(): Person
    where S : Named,
    S : Aged,
    S : PersonBuilder =
    Person(name, age)

    fun <R> newPerson(init: PersonBuilder.() -> R): Person
    where R : Named,
    R : Aged,
    R : PersonBuilder =
    PersonBuilder().run(init).build()

    fun <R> itPerson(init: (PersonBuilder) -> R): Person
    where R : Named,
    R : Aged,
    R : PersonBuilder =
    newPerson(init)

    优点:
  • 与 DSL 兼容。
  • 在指定姓名和年龄之前,无法构建一个人。
  • 仅必须指定已更改和所需的接口(interface)。 (在 Aged 函数中没有提到 name。)
  • 可以轻松添加新属性。
  • 部分构建的对象可以安全地保存到变量中并传递给函数。
  • 属性可以在赋值后使用。

  • 缺点:
  • 带有接收器的 Lambda 不能在 DSL 中使用,因为 Kotlin 不会推断 this 的类型引用。
  • 可以重新分配属性。
  • where 中的样板代码条款。
  • 不能明确指定变量类型(PersonBuilder & Named 不是有效的 Kotlin 语法)。
  • _name_age属性不能是私有(private)的,因为它们应该可以从扩展函数中访问。

  • 这是此构建器使用示例:
    newPerson {
    age(21)
    name("Bob")
    this // doesn't compile (this type isn't inferred)
    }
    itPerson {
    it.age(21)
    it.name("Ann")
    it
    }
    itPerson {
    it.age(21)
    it // doesn't compile
    }
    val builder = PersonBuilder()
    builder.name("Bob")
    builder.build() // doesn't compile
    builder.age(21)
    builder.build()

    有没有更好的空安全构建器实现,有没有办法摆脱我的实现缺点?

    最佳答案

    我认为契约(Contract)不适合您的问题,而建筑商“组合”可能适合。

    我的建议:

    class PersonBuilder(private val name: String, private val age: Int) {
    fun build() = Person(name, age)
    }

    class PersonNameBuilder(private val name: String) {

    fun withAge(age: Int) = PersonBuilder(name, age)
    }

    class PersonAgeBuilder(private val age: Int) {

    fun withName(name: String) = PersonBuilder(name, age)
    }

    data class Person(val name: String, val age: Int)

    用例:
    PersonNameBuilder("Bob").withAge(13).build() 
    PersonAgeBuilder(25).withName("Claire").build()

    PersonNameBuilder("Bob") // can't build(). Forced to add age!
    PersonAgeBuilder(25) // can't build(). Forced to add name!

    优点:
  • 在指定姓名和年龄之前无法 build 一个人
  • 无法重新分配属性。
  • 部分构建的对象可以安全地保存到变量中并传递给函数
  • 流畅的界面
  • 非常容易扩展、更改、重构、f.e.使用 labdas 和惰性执行
  • DSL可以轻松搞定
  • 如果有丰富的labdas在后台调用或执行某些东西-很容易测试,因为它在自己的单一类中
  • 有需要可以加泛型

  • 缺点:
  • 仅一个属性/字段的样板代码/类
  • 接收器类必须知道一个特定的(不同的)类而不是一个。
  • 关于generics - 如何强制客户端代码使用合约初始化 Kotlin 中所有必需的构建器字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58150954/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com