如何在Kotlin中实现Builder模式?
嗨,我是Kotlin世界的新手。 我喜欢迄今为止所看到的,并开始考虑将我们在应用程序中使用的一些库从Java转换为Kotlin。
这些库充满了Pojos,包括setter,getters和Builder类。 现在我已经用Googlesearch了解在Kotlin中实施build筑商的最佳方法,但没有成功。
第二次更新:现在的问题是如何编写一个简单的POJO的Builderdevise模式与Kotlin中的一些参数? 下面的代码是我通过编写java代码然后使用eclipse-kotlin-plugin转换成Kotlin的尝试。
class Car private constructor(builder:Car.Builder) { var model:String? = null var year:Int = 0 init { this.model = builder.model this.year = builder.year } companion object Builder { var model:String? = null private set var year:Int = 0 private set fun model(model:String):Builder { this.model = model return this } fun year(year:Int):Builder { this.year = year return this } fun build():Car { val car = Car(this) return car } } }
首先,在大多数情况下,您不需要在Kotlin中使用构build器,因为我们有默认和命名参数。 这使您能够编写
class Car(val model: String? = null, val year: Int = 0)
并像这样使用它:
val car = Car(model = "X")
如果你绝对想用build设者,你可以这样做:
使Builder成为companion object
是没有意义的,因为object
是单例。 相反,将其声明为一个嵌套类(在Kotlin中默认是静态的)。
将属性移动到构造函数中,以便可以以常规方式实例化对象(如果不应该使构造函数保持私有状态),并使用一个辅助构造函数,该构造函数需要构build器并委托给主构造函数。 代码如下所示:
class Car( //add private constructor if necessary val model: String?, val year: Int ) { private constructor(builder: Builder) : this(builder.model, builder.year) class Builder { var model: String? = null private set var year: Int = 0 private set fun model(model: String) = apply { this.model = model } fun year(year: Int) = apply { this.year = year } fun build() = Car(this) } }
用法: val car = Car.Builder().model("X").builder()
此代码可以通过使用构build器DSL来进一步缩短:
class Car ( val model: String?, val year: Int ) { private constructor(builder: Builder) : this(builder.model, builder.year) companion object { inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build() } class Builder { var model: String? = null var year: Int = 0 fun build() = Car(this) } }
用法: val car = Car.build { model = "X" }
如果需要某些值并且没有默认值,则需要将它们放入构build器的构造器中,也可以放在我们刚刚定义的build
方法中:
class Car ( val model: String?, val year: Int, val required: String ) { private constructor(builder: Builder) : this(builder.model, builder.year, builder.required) companion object { inline fun build(required: String, block: Builder.() -> Unit) = Builder(required).apply(block).build() } class Builder( val required: String ) { var model: String? = null var year: Int = 0 fun build() = Car(this) } }
用法: val car = Car.build(required = "requiredValue") { model = "X" }
因为我使用Jackson库来parsing来自JSON的对象,所以我需要一个空的构造函数,而且我不能有可选的字段。 所有的领域也必须是可变的。 然后我可以使用这个很好的语法,它和Builder模式做同样的事情:
val car = Car().apply{ model = "Ford"; year = 2000 }
我个人从未在Kotlin见过build造者,但也许只是我。
所有的validation需要在init
块中进行:
class Car(val model: String, val year: Int = 2000) { init { if(year < 1900) throw Exception("...") } }
在这里,我冒昧地猜测,你不是真的想要model
和year
是可以改变的。 此外,这些默认值似乎没有任何意义,(特别是name
为null
),但我留下一个用于演示的目的。
一个观点:在Java中使用的构build器模式意味着没有命名参数。 在具有命名参数的语言(如Kotlin或Python)中,具有长列表(也许是可选参数)的构造函数是一个很好的习惯。
我见过很多例子,宣布额外的乐趣作为build设者。 我个人喜欢这种方法。 节省编写build设者的努力。
package android.zeroarst.lab.koltinlab import kotlin.properties.Delegates class Lab { companion object { @JvmStatic fun main(args: Array<String>) { val roy = Person { name = "Roy" age = 33 height = 173 single = true car { brand = "Tesla" model = "Model X" year = 2017 } car { brand = "Tesla" model = "Model S" year = 2018 } } println(roy) } class Person() { constructor(init: Person.() -> Unit) : this() { this.init() } var name: String by Delegates.notNull() var age: Int by Delegates.notNull() var height: Int by Delegates.notNull() var single: Boolean by Delegates.notNull() val cars: MutableList<Car> by lazy { arrayListOf<Car>() } override fun toString(): String { return "name=$name, age=$age, " + "height=$height, " + "single=${when (single) { true -> "looking for a girl friend T___T" false -> "Happy!!" }}\nCars: $cars" } } class Car() { var brand: String by Delegates.notNull() var model: String by Delegates.notNull() var year: Int by Delegates.notNull() override fun toString(): String { return "(brand=$brand, model=$model, year=$year)" } } fun Person.car(init: Car.() -> Unit): Unit { cars.add(Car().apply(init)) } } }
我还没有find一种方法可以强制一些字段在DSL中初始化,如显示错误,而不是抛出exception。 让我知道,如果有人知道。
我会说在Kotlin中的模式和实现保持几乎相同。 有时可以跳过它,这要归功于默认值,但是对于更复杂的对象创build,构build器仍然是一个不可忽略的有用工具。
对于一个简单的类,你不需要一个单独的构build器。 您可以使用Kirill Rakhman所描述的可选构造函数参数。
如果你有更复杂的类,那么Kotlin提供了一种创buildGroovy风格的构build器/ DSL的方法:
https://kotlinlang.org/docs/reference/type-safe-builders.html
这里是一个例子:
https://github.com/dbacinski/Design-Patterns-In-Kotlin#builder–assembler
您可以在kotlin示例中使用可选参数:
fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String = "default") { System.out.printf("parameter %s %d %d %s\n", p1, p2, p3, p4) }
然后
myFunc("a") myFunc("a", 1) myFunc("a", 1, 2) myFunc("a", 1, 2, "b")