1. Introduction
In this quick article, we’ll see how to implement the Builder Design Pattern in Kotlin.
2. Builder Pattern
The Builder pattern is the one that people often use but rarely create on their own.
It’s great to handle the building of objects that may contain a lot of parameters and when we want to make the object immutable once we’re done constructing it.
To learn more, have a look at our tutorial on Creational Design Patterns here.
3. Implementation
Kotlin provides many useful features such as named and default parameters, apply() and data class which avoiding the use of classical Builder pattern implementation.
For that reason, we’ll see first a classical Java style implementation and then a more Kotlin style short form.
3.1. Java-Style Implementation
Let’s start creating one class – FoodOrder – which contains read-only fields since we don’t want outer objects to access them directly:
class FoodOrder private constructor(builder: FoodOrder.Builder) { val bread: String? val condiments: String? val meat: String? val fish: String? init { this.bread = builder.bread this.condiments = builder.condiments this.meat = builder.meat this.fish = builder.fish } class Builder { // builder code } }
Notice that the constructor is private so that only the nested Builder class can access in it.
Let’s now move on creating the nested class which will be used to build objects:
class Builder { var bread: String? = null private set var condiments: String? = null private set var meat: String? = null private set var fish: String? = null private set fun bread(bread: String) = apply { this.bread = bread } fun condiments(condiments: String) = apply { this.condiments = condiments } fun meat(meat: String) = apply { this.meat = meat } fun fish(fish: String) = apply { this.fish = fish } fun build() = FoodOrder(this) }
As we see, our Builder has the same fields as the outer class. For each outer field, we have a matching setter method.
In case we have one or more mandatory fields, instead of using setter methods, let’s make a constructor set them.
Note that we’re using the apply function in order to support the fluent design approach.
Finally, with the build method, we call the FoodOrder constructor.
3.2. Koltin-Style Implementation
In order to take full advantage of Kotlin, we have to revisit some best practices we got used to in Java. Many of them can be replaced with better alternatives.
Let’s see how we can write idiomatic Kotlin code:
class FoodOrder( val bread: String?, val condiments: String?, val meat: String?, val fish: String?) { data class Builder( var bread: String? = null, var condiments: String? = null, var meat: String? = null, var fish: String? = null) { fun bread(bread: String) = apply { this.bread = bread fun condiments(condiments: String) = apply { this.condiments = condiments } fun meat(meat: String) = apply { this.meat = meat } fun fish(fish: String) = apply { this.fish = fish } fun build() = FoodOrder(bread, condiments, meat, fish) } }
Kotlin comes with named and default parameters help to minimize the number of overloads and improve the readability of the function invocation.
We can also take advantage of Kotlin’s data class structure that we explore more in another tutorial here.
Finally, as well as in Java style implementation, apply() is useful for implementing fluent setters.
4. Usages Example
Briefly, let’s have a look at how to build FoodOrder objects using these Builder pattern implementation:
val foodOrder = FoodOrder.Builder() .bread("white bread") .meat("bacon") .condiments("olive oil") .build()
5. Conclusion
The Builder Pattern solves a very common problem in the object-oriented programming of how to flexibly create an immutable object without writing many constructors.
When considering a builder, we should focus on whether or not the construction is complex. If we have too simple construction patterns then the effort of creating our flexible builder object may far exceed the benefit.
As always, the code is available over on Github.