How to implement Builder pattern in Kotlin?
Solution 1:
First and foremost, in most cases you don't need to use builders in Kotlin because we have default and named arguments. This enables you to write
class Car(val model: String? = null, val year: Int = 0)
and use it like so:
val car = Car(model = "X")
If you absolutely want to use builders, here's how you could do it:
Making the Builder a companion object
doesn't make sense because object
s are singletons. Instead declare it as an nested class (which is static by default in Kotlin).
Move the properties to the constructor so the object can also be instantiated the regular way (make the constructor private if it shouldn't) and use a secondary constructor that takes a builder and delegates to the primary constructor. The code will look as follow:
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)
}
}
Usage: val car = Car.Builder().model("X").build()
This code can be shortened additionally by using a 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)
}
}
Usage: val car = Car.build { model = "X" }
If some values are required and don't have default values, you need to put them in the constructor of the builder and also in the build
method we just defined:
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)
}
}
Usage: val car = Car.build(required = "requiredValue") { model = "X" }
Solution 2:
One approach is to do something like the following:
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)
}
}
Usage sample:
val car = Car.Builder()
.model("Ford Focus")
.color("Black")
.type("Type")
.build()
Solution 3:
Because I'm using Jackson library for parsing objects from JSON, I need to have an empty constructor and I can't have optional fields. Also all fields have to be mutable. Then I can use this nice syntax which does the same thing as Builder pattern:
val car = Car().apply{ model = "Ford"; year = 2000 }
Solution 4:
I personally have never seen a builder in Kotlin, but maybe it is just me.
All validation one needs happens in the init
block:
class Car(val model: String,
val year: Int = 2000) {
init {
if(year < 1900) throw Exception("...")
}
}
Here I took a liberty to guess that you don't really wanted model
and year
to be changeable. Also those default values seems to have no sense, (especially null
for name
) but I left one for demonstration purposes.
An Opinion: The builder pattern used in Java as a mean to live without named parameters. In languages with named parameters (like Kotlin or Python) it is a good practice to have constructors with long lists of (maybe optional) parameters.