How to implement Builder pattern in Kotlin?
嗨,我是Kotlin世界的新手。 我喜欢到目前为止所看到的内容,并开始考虑将我们在应用程序中使用的某些库从Java转换为Kotlin。
这些库充满了带有setter,getter和Builder类的Pojo。 现在,我已经在Google上搜寻以找到在Kotlin中实施Builders的最佳方法,但是没有成功。
第二次更新:问题是如何在Kotlin中为带有某些参数的简单pojo编写一个Builder设计模式? 下面的代码是我的尝试,方法是编写Java代码,然后使用eclipse-kotlin-plugin转换为Kotlin。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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中使用构建器,因为我们有默认和命名参数。这使您可以编写
1 | class Car(val model: String? = null, val year: Int = 0) |
并像这样使用它:
1 | val car = Car(model ="X") |
如果您绝对要使用构建器,请按以下步骤操作:
将Builder设为
将属性移至构造函数,以便也可以以常规方式实例化该对象(如果不应该将该构造函数设为私有),并使用辅助构造函数,该辅助构造函数接受一个构造函数并将其委托给主要构造函数。该代码将如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) } } |
用法:
可以使用构建器DSL进一步缩短此代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) } } |
用法:
如果某些值是必需的并且没有默认值,则需要将它们放在生成器的构造函数中以及我们刚刚定义的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) } } |
用法:
因为我正在使用Jackson库从JSON解析对象,所以我需要有一个空的构造函数,并且不能有可选字段。而且所有字段都必须是可变的。然后,我可以使用这种不错的语法,该语法与Builder模式相同:
1 | val car = Car().apply{ model ="Ford"; year = 2000 } |
一种方法是执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Car( val model: String?, val color: String?, val type: String?) { data class Builder( var model: String? = null, var color: String? = null, var type: String? = null) { fun model(model: String) = apply { this.model = model } fun color(color: String) = apply { this.color = color } fun type(type: String) = apply { this.type = type } fun build() = Car(model, color, type) } } |
用法样本:
1 2 3 4 5 | val car = Car.Builder() .model("Ford Focus") .color("Black") .type("Type") .build() |
我个人从未在Kotlin见过建筑商,但也许只有我一个人。
所有验证一项需求都发生在
1 2 3 4 5 6 7 | class Car(val model: String, val year: Int = 2000) { init { if(year < 1900) throw Exception("...") } } |
在这里,我冒昧地猜测您真的不希望
一个观点:
Java中使用的构建器模式,它是没有命名参数的生存方式。在具有命名参数的语言中(例如Kotlin或Python),最好让构造函数带有一长串(可能是可选的)参数的构造函数。
我看到了许多示例,这些示例声明了作为构建器的额外乐趣。我个人喜欢这种方法。节省编写构建器的工作量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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!!" }} Cars: $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中初始化某些字段,例如显示错误而不是引发异常。让我知道是否有人知道。
我迟到聚会了。 如果必须在项目中使用Builder模式,我也会遇到同样的难题。 后来,经过研究,我意识到这绝对是不必要的,因为Kotlin已经提供了命名参数和默认参数。
如果您确实需要实施,那么Kirill Rakhman的答案是如何以最有效的方式实施的可靠答案。 您可能会发现有用的另一件事是https://www.baeldung.com/kotlin-builder-pattern,您可以在实现上与Java和Kotlin进行比较和对比
如今,人们应该检查Kotlin的Type-Safe Builders。
使用上述对象创建方式将如下所示:
1 2 3 4 5 6 | html { head { title {+"XML encoding with Kotlin"} } // ... } |
vaadin-on-kotlin框架是一个很好的"实际应用"用法示例,该框架利用类型安全的构建器来组装视图和组件。
对于简单的类,您不需要单独的构建器。您可以按照Kirill Rakhman所述使用可选的构造函数参数。
如果您有更复杂的类,那么Kotlin提供了一种创建Groovy样式的Builders / DSL的方法:
类型安全的建筑商
这是一个例子:
Github示例-建造者/组装者
我正在一个Kotlin项目中工作,该项目公开了Java客户端使用的API(无法利用Kotlin语言构造)。我们必须添加构建器以使其在Java中可用,因此我创建了一个@Builder批注:https://github.com/ThinkingLogic/kotlin-builder-annotation
-基本上是Kotlin的Lombok @Builder注释的替代品。
我使用以下代码在Kotlin中实现了基本的Builder模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | data class DialogMessage( var title: String ="", var message: String ="" ) { class Builder( context: Context){ private var context: Context = context private var title: String ="" private var message: String ="" fun title( title : String) = apply { this.title = title } fun message( message : String ) = apply { this.message = message } fun build() = KeyoDialogMessage( title, message ) } private lateinit var dialog : Dialog fun show(){ this.dialog= Dialog(context) . . . dialog.show() } fun hide(){ if( this.dialog != null){ this.dialog.dismiss() } } } |
最后
Java的:
1 2 3 4 5 | new DialogMessage.Builder( context ) .title("Title") .message("Message") .build() .show(); |
科特林:
1 2 3 4 5 | DialogMessage.Builder( context ) .title("Title") .message("") .build() .show() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class Foo private constructor(@DrawableRes requiredImageRes: Int, optionalTitle: String?) { @DrawableRes @get:DrawableRes val requiredImageRes: Int val optionalTitle: String? init { this.requiredImageRes = requiredImageRes this.requiredImageRes = optionalTitle } class Builder { @DrawableRes private var requiredImageRes: Int = -1 private var optionalTitle: String? = null fun requiredImageRes(@DrawableRes imageRes: Int): Builder { this.intent = intent return this } fun optionalTitle(title: String): Builder { this.optionalTitle = title return this } fun build(): Foo { if(requiredImageRes == -1) { throw IllegalStateException("No image res provided") } return Foo(this.requiredImageRes, this.optionalTitle) } } } |
您可以在kotlin中使用可选参数
例:
1 2 3 4 | fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String ="default") { System.out.printf("parameter %s %d %d %s ", p1, p2, p3, p4) } |
然后
1 2 3 4 | myFunc("a") myFunc("a", 1) myFunc("a", 1, 2) myFunc("a", 1, 2,"b") |
我要说的是,模式和实现在Kotlin中几乎保持不变。有时由于默认值而可以跳过它,但是对于更复杂的对象创建而言,构建器仍然是一个不可忽略的有用工具。