Builder Pattern

The Builder pattern is one of the behavioral software patterns, described in the book of the Gang-Of-Four. It gives a flexible solution to various object creation problems. The intent of the builder is to centralize the construction process of an object, which is difficult to build. In this way the construction and representation of an object are separated.

The Builder pattern addresses the following problems:

  • How can a class create different representation of a complex object?
  • How can the construction process be simplified and unified?
  • Encapsulate creation and assemble of objects.

Our example scenario

We are having two classes: The House and Room Class. Both can have a specific color (here represented by a simple Integer). The House Class has 0 to many Rooms.

House and Room class

class Room(private var colorCode : Int)

class House(private var color : Int,
            private var rooms : ArrayList<Room>)

Example construction

var room1 = Room(10)
var room2 = Room(20)
var roomList = arrayListOf<Room>(room1, room2)
var house = House(10, roomList)

At this point this doesn’t look to terrible, but one gets already a feeling that the construction of a house is quite cumbersome. However, when the House becomes more complex, this will lead to a lot of boilerplate code. Imagine this House class is used in several places, then at every place the code must be duplicated. Again, this is a hotspot for potential bugs.

A solution is needed, where the process of construction is centralized. This is where the Builder pattern comes into the play.

UML Diagramm Builder Pattern

Builder pattern uml diagram

Following our example, we are going to implement the Builder Pattern in Kotlin with a new class called HouseBuilder. The key point of the builder is that it provides a function which returns a complete House and additional function to set some properties to the house. The additional functions always return a reference to the current builder. In this way the assemble process can be “chained” together. The builder can than easily used.

HouseBuilder Class

class HouseBuilder{
    private var rooms = ArrayList<Room>()
    private var colorCode = 0

    fun build() : House {
        return House(colorCode, rooms)
    }

    fun setColor(color : Int) : HouseBuilder {
        this.colorCode = color
        return this
    }

    fun addRoom(color : Int) : HouseBuilder {
        var room = Room(color)
        rooms.add(room)
        return this
    }
}

Usage of the HouseBuilder Class

val houseBuilder = HouseBuilder()
val house = houseBuilder
    .setColor(10)
    .addRoom(20)
    .addRoom(30)
    .build()
val house2 = houseBuilder.build()

Abstract Builder Pattern

To go one step further it is interesting to put common construction / assembling steps in a common base class or interface. Derived classes can than just adapt parts of the assembly process. With that modification the construction is even more separated and decoupled from the usage of the house. Even more this design follows the Open-Closed and Single Responsibility Principles. In our case we use an interface with default implementation.

IHouseBuilder and concrete implementations

interface IHouseBuilder{
    var rooms : ArrayList<Room>
    var colorCode : Int

    fun build() : House {
        return House(colorCode, rooms)
    }
    
    fun addRoom() : IHouseBuilder{
        var room = Room(colorCode)
        rooms.add(room)
        return this
    }
}

class RedHouseBuilder : IHouseBuilder{
    override var rooms = ArrayList<Room>()
    override var colorCode = 10    
}

class BlueHouseBuilder : IHouseBuilder{
    override var rooms = ArrayList<Room>()
    override var colorCode = 20
}

Usage of the HouseBuilder derived classes

val redHouseBuilder = RedHouseBuilder()
val redHouse = redHouseBuilder
    .addRoom()
    .addRoom()
    .build()

val blueHouseBuilder = BlueHouseBuilder()
val blueHouse = blueHouseBuilder
    .addRoom()
    .addRoom()
    .build()

Conclusion

We implemented the builder design pattern in Kotlin. This software pattern gives more flexibility in case of construction processes of complex objects. By using default implementations, the construction process is even more decoupled and reusable.