在JetBrains开放日2019上,据说Kotlin团队研究了合同并试图实施context合同,这些合同只允许在某些情况下调用函数,例如,仅允许调用函数build
如果setName
方法恰好在其之前调用过一次。 Here是通话录音。
我已经尝试使用当前可用的Kotlin功能来模拟此类合同,以为data class Person(val name: String, val age: Int)
创建一个空安全的构建器。
注意:当然,在这种情况下,使用命名参数而不是构建器模式要容易得多,但是命名参数不允许将未完全构建的对象解析为其他函数,并且在需要时很难使用它们创建一个由其他复杂对象等组成的复杂对象。
所以这是我的null安全构建器实现:
基于通用标记的构建器
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)
优点:
name
和age
,否则无法建造人。缺点:
age
,它也必须声明它接受具有任何AGE
类型参数的构建器,并返回具有相同类型参数的构建器。)] >_name
和_age
属性不能是私有的,因为应该可以从扩展功能访问它们。这是此构建器的使用示例:
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)
基于合同的构建器
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)
优点:
Aged
函数中没有提及name
。)缺点:
this
参考的类型。where
子句中的样板代码。PersonBuilder & Named
不是有效的Kotlin语法。_name
和_age
属性不能是私有的,因为应该可以从扩展功能访问它们。这是此构建器的使用示例:
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()
是否有更好的null安全生成器实现,并且有什么方法可以摆脱我的实现弊端?
[在JetBrains开放日2019上,据说Kotlin团队研究了合同并试图实施上下文合同,该合同只允许在某些上下文中调用函数,例如,函数...
我认为合同不适合您的问题,而构建器的“组合”可能适合。