如何在Kotlin中实现Builder模式?

嗨,我是Kotlin世界的新手。 我喜欢迄今为止所看到的,并开始考虑将我们在应用程序中使用的一些库从Java转换为Kotlin。

这些库充满了Pojos,包括setter,getters和Builder类。 现在我已经用Google搜索了解在Kotlin中实施建筑商的最佳方法,但没有成功。

第二次更新:现在的问题是如何编写一个简单的POJO的Builder设计模式与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中使用构建器,因为我们有默认和命名参数。 这使您能够编写

 class Car(val model: String? = null, val year: Int = 0) 

并像这样使用它:

 val car = Car(model = "X") 

如果你绝对想用建设者,你可以这样做:

使Builder成为companion object是没有意义的,因为object是单例。 相反,将其声明为一个嵌套类(在Kotlin中默认是静态的)。

将属性移动到构造函数中,以便可以以常规方式实例化对象(如果不应该使构造函数保持私有状态),并使用一个辅助构造函数,该构造函数需要构建器并委托给主构造函数。 代码如下所示:

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

此代码可以通过使用构建器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方法中:

 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库来解析来自JSON的对象,所以我需要一个空的构造函数,而且我不能有可选的字段。 所有的领域也必须是可变的。 然后我可以使用这个很好的语法,它和Builder模式做同样的事情:

 val car = Car().apply{ model = "Ford"; year = 2000 } 

我个人从未在Kotlin见过建造者,但也许只是我。

所有的验证需要在init块中进行:

 class Car(val model: String, val year: Int = 2000) { init { if(year < 1900) throw Exception("...") } } 

在这里,我冒昧地猜测,你不是真的想要modelyear是可以改变的。 此外,这些默认值似乎没有任何意义,(特别是namenull ),但我留下一个用于演示的目的。

一个观点:在Java中使用的构建器模式意味着没有命名参数。 在具有命名参数的语言(如Kotlin或Python)中,具有长列表(也许是可选参数)的构造函数是一个很好的习惯。

我见过很多例子,宣布额外的乐趣作为建设者。 我个人喜欢这种方法。 节省编写建设者的努力。

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

我还没有找到一种方法可以强制一些字段在DSL中初始化,如显示错误,而不是抛出异常。 让我知道,如果有人知道。

我会说在Kotlin中的模式和实现保持几乎相同。 有时可以跳过它,这要归功于默认值,但是对于更复杂的对象创建,构建器仍然是一个不可忽略的有用工具。

对于一个简单的类,你不需要一个单独的构建器。 您可以使用Kirill Rakhman所描述的可选构造函数参数。

如果你有更复杂的类,那么Kotlin提供了一种创建Groovy风格的Builders / 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")