How to implement Builder pattern in Kotlin?
Asked Answered
L

18

225

Hi I am a newbie in the Kotlin world. I like what I see so far and started to think to convert some of our libraries we use in our application from Java to Kotlin.

These libraries are full of Pojos with setters, getters and Builder classes. Now I have googled to find what is the best way to implement Builders in Kotlin but no success.

2nd Update: The question is how to write a Builder design-pattern for a simple pojo with some parameters in Kotlin? The code below is my attempt by writing java code and then using the eclipse-kotlin-plugin to convert to 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
        }
    }
}
Latonya answered 21/3, 2016 at 20:19 Comment(7)
do you need model and year to be mutable? Do you change them after a Car creation?Girand
I guess they should be immutable yes. Also you want to be sure they are set both and not emptyLatonya
You can also use this github.com/jffiorillo/jvmbuilder Annotation Processor to generate the builder class automatically for you.Wolfe
@Wolfe Good idea to add it to standard kotlin. It is useful for libraries written in kotlin.Latonya
github.com/ThinkingLogic/kotlin-builder-annotationChirography
Unless a class has a huge number of constructor params, Kotlin features such as optional, named and property params can easily replace the Builder pattern.Dishwater
Most of the answers miss one basic but important use of a builder, that is to build an immutable object incrementally. There are myriad uses of this, for example, when parsing an input. Creating a new data class for each event would be completely wasteful in that case.Cherycherye
E
425

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 objects 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" }

Employ answered 22/3, 2016 at 9:48 Comment(19)
@kirill seriously, what's the gain compared to a long list of (optional) parameters in a constructor? Like in a real-life situation?Girand
Nothing, but the author of the question specifically asked how to implement the builder pattern.Employ
@KirillRakhman "a pattern" by its definition is a code construct that people often use to solve a particular problem. The Builder boilerplate solves nothing in Kotlin, and that's why it is not used. I thing it is worth mentioning somewhere in your answer, for people not to thing this is something idiomaticGirand
I should correct myself, the builder pattern has some advantages, e.g. you could pass a partially constructed builder to another method. But you're right, I'll add a remark.Employ
Actually in the beginning my question was if builder is needed in Kotlin and if so how to implement but then I got a remark that my question was not clear enough so I removed the part asking if Builder is useful in Kotlin. However I still think Kirills code is very good and useful in understanding Kotlin.Latonya
@KirillRakhman how about calling the builder from java? Is there an easy way to make the builder available to java?Latonya
All three versions can be called from Java like so: Car.Builder builder = new Car.Builder();. However only the first version has a fluent interface so the calls to the second and third versions can't be chained.Employ
@Girand you mean (optional) parameters in the constructor of the POJO?Freeway
How can you set more than one property when using the second or third implementations shown here?Margy
By the way, in the first implementation you can write the setter methods more concisely: fun model(model: String) = apply { this.model = model }Margy
The builder patter not only allow you to use partially constructed object, but the main purpose - to avoid mistakes and increase code readability. Instead of new Car("A", "B", "C", "D") you will have new Car.Builder().model("A").owner("B").company("C").build().Jablonski
I think the kotlin example at the top only explains one possible use case. The main reason I use builders is to convert a mutable object into an immutable one. That is, I need to mutate it over time while I'm "building" and then come up with an immutable object. At least in my code there are only one or 2 examples of code that has so many variations of parameters that I would use a builder instead of several different constructors. But to make an immutable object, I have a few cases where a builder is definitely the cleanest way I can think of.Basophil
I'm also new on Kotlin and have been working through the Kotlin In Action book. In chapter 5.5.2, when it describe the apply function, it also specific state that this is the Kotlin way for builder pattern. With both named argument and apply, is there any legit reason that one would want to explicit implement a Builder in Kotlin?Dolce
@Basophil If you can make it data class, Kotlin generates a copy function with the same args as the constructor but with default values from the instance. So if performance is not an issue you can use that. Or you can write the copy function yourself: class Person(val name: String, val age: Int) { fun copy( name: String = this.name, age: Int = this.age ) = Person(name, age) }Rockery
@KirillRakhman I think the example usage should be val car = Car.Builder().model("X").build() instead of .builder().Luellaluelle
I created some sample code to illustrate the different approaches for the builder pattern in Kotlin here: github.com/1gravity/design_patterns/tree/main/src/main/kotlin/…. On top of the proposed solution I created a DSL based variation.La
@Dolce maybe you're implementing something where you get the data piece by piece in individual callbacks (making it impractical to wrap a block around the logic), or for some other reason, you end up wanting to set values some times and other times leave them as the default.Irresoluble
Nice ! , i'd like to see what you propose to create à fluent api in kotlinBonilla
+1 for not needing to use Builders in the first place! Please don't use them in the sake of Kotlin! They should only be used for Java interop.Costate
H
79

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()
Halsy answered 5/4, 2019 at 11:9 Comment(3)
Thanks a lot! You made my day! Your answer should be marked as SOLUTION.Caseous
But why? This is just not necessary in Kotlin, bloated, not null-safe and error prone. :( You could even do some validation by just providing an init {} block. Please don't force outdated Java patterns into Kotlin.Cabaret
Because you might need to instantiate the Car class in Java code.Togetherness
G
12

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.

Girand answered 22/3, 2016 at 5:0 Comment(7)
Thanks a lot for the answer. I like your approach but the downside is for a class with many parameters it becomes not so friendly to use the constructor and also test the class.Latonya
+Keyhan two other ways you can do validation, assuming the validation doesn't happen between the fields: 1) use property delegates where the setter does validation - this is pretty much the same thing as having a normal setter that does validation 2) Avoid primitive obsession and create new types to pass in that validate themselves.Blomquist
@Latonya this is a classic approach in Python, it works very well even for functions with tens of arguments. The trick here is to use named arguments (not available in Java!)Girand
Yes, it is also a solution worth using, it seems unlike java where builder class have some clear advantages, in Kotlin it is not so obvious, talked to C# developers, C# also have kotlin like features (default value and you could name params when calling constructor) they did not use builder pattern either.Latonya
I'd argue against that approach, if some logic to construct an instance is needed. This approach mixes a simple POJO/Data-Class with its construction and that is against the "Single Responsibility" and the "Closed for modification, open for extension"-PrincipleFreeway
Would you say Builder in Kotlin is more appropriate for mixed code base that contain both Java and Kotlin? If from the Java side we would need to frequently access a Kotlin object, it won't look too pretty if said object has 10 params in its constructor, is it?Dolce
@Dolce many of such cases can be solved with @JvmOverloads kotlinlang.org/docs/reference/…Girand
P
10

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 }
Prefatory answered 29/6, 2016 at 11:41 Comment(5)
In Jackson you don't actually need to have an empty constructor, and fields don't need to be mutable. You just have to annotate your constructor parameters with @JsonPropertyJebel
You don't even have to annotate with @JsonProperty anymore, if you compile with the -parameters switch.Ommatidium
Jackson can actually be configured to use a builder.Latonya
If you add the jackson-module-kotlin module to your project, you can just use data classes and it will work.Craunch
How is this doing the same thing as a Builder Pattern? You are instantiating the final product and then swapping out / adding information. The whole point of the Builder pattern is to not be able to get the final product until all the necessary information is present. Removing the .apply() leaves you with an undefined car. Removing all the constructor arguments from Builder leaves you with a Car Builder, and if you try to build it into a car, you'll likely run into an exception for not having specified the model and year yet. They are not the same thing.Vocative
G
7

I have seen many examples that declare extra funs as builders. I personally like this approach. Save effort to write builders.

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

    }
}

I have not yet found a way that can force some fields to be initialized in DSL like showing errors instead of throwing exceptions. Let me know if anyone knows.

Guidance answered 6/6, 2017 at 2:3 Comment(0)
R
3

For a simple class you don't need a separate builder. You can make use of optional constructor arguments as Kirill Rakhman described.

If you have more complex class then Kotlin provides a way to create Groovy style Builders/DSL:

Type-Safe Builders

Here is an example:

Github Example - Builder / Assembler

Rhody answered 24/10, 2016 at 20:48 Comment(1)
Thanks, but I was thinking of using it from java as well. As far as I know optional arguments would not work from java.Latonya
C
2

People nowdays should check Kotlin's Type-Safe Builders.

Using said way of object creation will look something like this:

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ...
}

A nice 'in-action' usage example is the vaadin-on-kotlin framework, which utilizes typesafe builders to assemble views and components.

Cutlery answered 1/11, 2018 at 12:52 Comment(0)
G
1

I would say the pattern and implementation stays pretty much the same in Kotlin. You can sometimes skip it thanks to default values, but for more complicated object creation, builders are still a useful tool that can't be omitted.

Garrett answered 21/3, 2016 at 20:27 Comment(2)
As far as constructors with default values you can even do validation of input using initializer blocks. However, if you need something stateful (so that you don't have to specify everything up front) then the builder pattern is still the way to go.Orthodox
Could you give me a simple example with code? Say a simple User class with name and email field with validation for email.Latonya
E
1

I was working on a Kotlin project that exposed an API consumed by Java clients (which can't take advantage of the Kotlin language constructs). We had to add builders to make them usable in Java, so I created an @Builder annotation: https://github.com/ThinkingLogic/kotlin-builder-annotation - it's basically a replacement for the Lombok @Builder annotation for Kotlin.

Exodontist answered 18/3, 2019 at 11:36 Comment(0)
L
1

I am late to the party. I also encountered the same dilemma if I had to use Builder pattern in the project. Later, after research I have realized it is absolutely unnecessary since Kotlin already provides the named arguments and default arguments.

If you really need to implement, Kirill Rakhman's answer is solid answer on how to implement in most effective way. Another thing you may find it useful is https://www.baeldung.com/kotlin-builder-pattern you can compare and contrast with Java and Kotlin on their implementation

Liddie answered 21/6, 2019 at 18:52 Comment(0)
B
1

A little changed and improved version of answers above

class MyDialog {
  private var title: String? = null
  private var content: String? = null
  private var confirmButtonTitle: String? = null
  private var rejectButtonTitle: String? = null

  @DrawableRes
  private var icon: Int? = null


  fun show() {
    // set dialog content here and show at the end
  }

  class Builder {
      private var dialog: MyDialog = MyDialog()

      fun title(title: String) = apply { dialog.title = title }

      fun icon(@DrawableRes icon: Int) = apply { dialog.icon = icon }

      fun content(content: String) = apply { dialog.content = content }

      fun confirmTitle(confirmTitle: String) = apply { dialog.confirmButtonTitle = confirmTitle }

      fun rejectButtonTitle(rejectButtonTitle: String) = apply { dialog.rejectButtonTitle = rejectButtonTitle }

      fun build() = dialog
  }
}

And usage

MyDialog.Builder()
        .title("My Title")
        .content("My content here")
        .icon(R.drawable.bg_edittext)
        .confirmTitle("Accept")
        .rejectButtonTitle("Cancel")
        .build()
        .show()
Berryman answered 13/5, 2022 at 15:46 Comment(1)
My dude, your code doesn't work, you are returning freshly created instance of MyDialog in build() method :)Twinkling
U
0
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)
        }

    }

}
Usquebaugh answered 25/5, 2018 at 21:59 Comment(0)
B
0

I implemented a basic Builder pattern in Kotlin with the follow code:

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

And finally

Java:

new DialogMessage.Builder( context )
       .title("Title")
       .message("Message")
       .build()
       .show();

Kotlin:

DialogMessage.Builder( context )
       .title("Title")
       .message("")
       .build()
       .show()
Bieber answered 16/11, 2018 at 1:32 Comment(0)
K
0
class Person(
    val name:String,
    val family:String,
    val age:Int,
    val nationalCode: String?,
    val email: String?,
    val phoneNumber: String?
) {

    // Private constructor
    private constructor(builder: Builder) : this (
        builder.name,
        builder.family,
        builder.age,
        builder.nationalCode,
        builder.email,
        builder.phoneNumber
    )

    // Builder class

    // 1 Necessary parameters in Builder class : name , family
    class Builder(val name :String,val family :String) {

        // 2 Optional parameters in Builder class :
        var age: Int = 0
            private set
        var nationalCode: String? = null
            private set
        var email: String? = null
            private set
        var phoneNumber: String? = null
            private set

        fun age(age: Int) = apply { this.age = age }
        fun nationalCode(nationalCode: String) =
            apply { this.nationalCode = nationalCode }
        fun email(email: String) = apply { this.email = email }
        fun phoneNumber(phoneNumber: String) =
            apply { this.phoneNumber = phoneNumber }

        // 3 Create
        fun create() = Person(this)

    }
}

for accessing :

val firstPerson = Person.Builder(
    name = "Adnan",
    family = "Abdollah Zaki")
    .age(32)
    .email("[email protected]")
    .phoneNumber("+989333030XXX")
    .nationalCode("04400XXXXX")
    .create()

val secondPerson = Person.Builder(
    name = "Foroogh",
    family = "Varmazyar")
    .create()
Khalsa answered 17/3, 2022 at 10:29 Comment(1)
This works in Java too?Latonya
H
0

I just found a fun way to create builder in kotlin:

enter image description here

As you can see, moduleBuilder can be reuse for other grafana build.

Here is the code:

class Grafana(
    private val module: String,
    private val scene: String,
    private val action: String,
    private val metric: String
) {
    companion object {
        fun build(module: String, scene: String, action: String, metric: String) =
            Grafana(module, scene, action, metric)

        val builder = ::build.curriedBuilder()

        private fun <P1, P2, P3, P4, R> Function4<P1, P2, P3, P4, R>.curriedBuilder() =
            fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = fun(p4: P4) = this(p1, p2, p3, p4)
    }

    fun report() = Unit
}


val moduleBuilder = Grafana.builder("module")
val scene = moduleBuilder("scene")
val gfA = scene("action")("metric")
gfA.report()

val sceneB = moduleBuilder("sceneB")
val gfB = sceneB("action")("metric")
gfB.report()

val gfC = Grafana.builder("xx")("xxx")("xxxx")("xxxx")
gfC.report()

Huddle answered 26/4, 2022 at 9:52 Comment(0)
C
0

The only thing I have not seen mentioned here is the fact that the builder functionality is completely covered by the combination of the already mentioned default values and the .copy() method.

The only use case that the presence of named parameters with default values doesn't cover is the one where you might want to populate the builder gradually with data that you receive asynchronously and actually build your class at some later point once you have all the ingredients. Kotlin enables this by virtue of the .copy() method implemented by all data classes letting you build your class gradually without much hassle and zero boilerplate, leveraging the power of named parameters.

No need to use builders in Kotlin at all. You will need to continue using/understanding them as they are used in your Java dependencies but they should start becoming a thing of the past.

Collar answered 23/8, 2023 at 8:59 Comment(2)
The result should be also accessible from Java. Is this so?Latonya
i am not sure how default values and the default methods of data classes get translated into java code. usually I use java classes in my kotlin files and not the other way around. but as seen from this link gist.github.com/kewp/1efc1a4c406577342c43ccb258bf8739 a bunch of methods are autogenerated that give you the functionality of a builder... among othersCollar
C
0
data class Car(
    val model: String,// NonNull
    val color: String?,// Nullable
    val type: String// NonNull
) {
    init {
        // do check between variables 
        if (color != "black" && color != "white") {
            throw Exception()
        }
    }
}

// usage:

   val car =  Car(....)
Culver answered 18/10, 2023 at 3:31 Comment(0)
E
-2

you can use optional parameter in kotlin example:

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

then

myFunc("a")
myFunc("a", 1)
myFunc("a", 1, 2)
myFunc("a", 1, 2, "b")
Erle answered 24/5, 2017 at 1:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.