Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/kotlin/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Generics 如何强制客户端代码使用合同初始化Kotlin中所有必需的生成器字段?_Generics_Kotlin_Type Inference_Contract - Fatal编程技术网

Generics 如何强制客户端代码使用合同初始化Kotlin中所有必需的生成器字段?

Generics 如何强制客户端代码使用合同初始化Kotlin中所有必需的生成器字段?,generics,kotlin,type-inference,contract,Generics,Kotlin,Type Inference,Contract,在2019年JetBrains开放日,据说Kotlin团队研究了合同,并试图实现只允许在某些上下文中调用函数的上下文合同,例如,只有在调用方法之前一次调用函数setName时,才允许调用函数build。这是一段谈话录音 我尝试用当前可用的Kotlin特性模拟这种契约,为数据类Person(val name:String,val age:Int)创建空安全生成器 注意:当然,在这种情况下,使用命名参数而不是生成器模式要容易得多,但是命名参数不允许将未完全构建的对象解析为其他函数,并且当您想要创建由

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

我尝试用当前可用的Kotlin特性模拟这种契约,为
数据类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)
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)
基于合同的建筑商

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)
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)


有没有更好的空安全构建器实现,有没有什么方法可以消除我的实现缺点?

我认为契约不适合您的问题,而构建器“组合”可能适合您的问题

我的建议:

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!
优点:

  • 在指定姓名和年龄之前,无法构建人
  • 无法重新分配属性
  • 部分构建的对象可以安全地保存到变量并传递给函数
  • 流畅的界面
  • 非常容易扩展、更改、重构,例如使用labdas和延迟执行
  • DSL可以很容易地完成
  • 如果使用labdas进行了丰富,可以在后台调用或执行某些内容,因为它位于自己的单个类中,因此非常容易测试
  • 如果需要,可以添加泛型
  • 缺点:

  • 一个属性/字段的样板代码/类
  • 接收方类必须知道一个特定的(不同的)类,而不是一个

  • 在添加几个字段之前,您的解决方案是很好的。例如,如果您添加
    gender
    字段,则不仅要添加
    PersonGenderBuilder
    ,还要添加
    PersonNameAgeBuilder
    PersonNameGenderBuilder
    PersonAgeGenderBuilder
    。n个字段需要2^n-1个类。这正是我在设计中试图避免的。很多代码都是如此。我想到的另一个解决方法是定义一个映射,该映射将存储字段“set”状态。然后,每个setter都会首先询问map是否已经设置了值。这将产生许多setter,我们无法在运行时看到它,但我们避免使用新类,我不太明白。无论如何,通过使用
    MutableMap
    (或
    MutableSet
    ),您只能实现运行时验证,我更希望在不正确使用生成器的情况下获得编译时错误。注意:最好使用属性委托而不是映射。是否需要编译时或运行时检查?