Search code examples
design-patternskotlin

How to implement Builder pattern in Kotlin?


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

Solution

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