About the Abstract-Step-Builder Pattern in Kotlin
Tags: kotlin, patterns
Published: 2021-01-10 19:53:08

Find the code for this article here: https://github.com/jonrosner/abstract-step-builder

Today I was faced with the challenge to improve the developer experience for one of our libraries. Explicitly the problem was with the construction of a few objects. It was clear that the construction was unintuitive and cumbersome, since most of the attributes were optional but some of them have dependencies on others. Meaning if you set one, you have to set the other as well. Let me give a quick example of this:

Imagine you are writing a library that you can plan houses with. This library will have a class `Kitchen`. Now imagine that a kitchen can have several items inside but some of these items are conditionally coupled to others. For example, if a kitchen has a fridge, it also needs a power-socket. One trivial solution to this is to create several constructors:

```kotlin
constructor(fridge: Fridge, powerSocket: PowerSocket) : this() {
    this.fridge = fridge
    this.powerSocket = powerSocket
}
```

This is fine until one day you implement new items that can be added to our room. For example, a sink that in turn requires a water pipe. Of course, we could add new constructors but one can see how this exponentially gets worse. We would already need a total of 4 constructors for just two items. A third item would boost this number to 9 and so on. Seeing a class with so many constructors is usually a sign of bad design.

```kotlin
constructor(fridge: Fridge, powerSocket: PowerSocket) : this() {
    this.fridge = fridge
    this.powerSocket = powerSocket
}

constructor(sink: Sink, waterPipe: WaterPipe) : this() {
    this.sink = sink
    this.waterPipe = waterPipe
}

constructor(fridge: Fridge, powerSocket: PowerSocket, sink: Sink, waterPipe: WaterPipe) : this() {
    this.fridge = fridge
    this.powerSocket = powerSocket
    this.sink = sink
    this.waterPipe = waterPipe
}
```

"So why not just make one constructor with all parameters as optional?" 

This is possible but creates an awful developer experience. Remember that some items require other items, so a dev would have to remember that he has to provide a power-socket when he provides a fridge, there is no way for the IDE to tell him so. If a dev forgets to add a power-socket after adding a fridge, the program will crash during runtime. This is the worst possible outcome and we want to avoid this at all costs.

The first solution I tried was the **Builder pattern**. Quoting Wikipedia: "The builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation. It is one of the Gang of Four design patterns." Sounds like a fit for our problem right? Let's implement it.

```kotlin
class KitchenBuilder {
    private var fridge: Fridge? = null
    private var powerSocket: PowerSocket? = null

    fun setFridge(fridge: Fridge) : KitchenBuilder {
        this.fridge = fridge
        return this
    }

    fun setPowerSocket(powerSocket: PowerSocket) : KitchenBuilder {
        this.powerSocket = powerSocket
        return this
    }

    fun build(): Kitchen {
        return Kitchen(fridge, powerSocket, sink, waterPipe)
    }
}
```

As seen in the code above, the builder pattern defines a new class, called the Builder. The builder basically instantiates the desired object once the `.build()` method is called. Before that, it aggregates the desired constructional-choices and always just returns itself.

This solves the problem of having to add exponentially more constructors when new items are added or having to change old constructor signatures. We simply have to add a new step to the builder if we want to add a new item. But unfortunately, this is not enough yet. How does the builder know which items rely on which? If a dev would call `Kitchen.Builder().addFridge().build()` he would be able to get a fridge without a power-socket, meaning the code would break during runtime. BAD.

To prevent this we extend the builder pattern using a clever integration of interfaces resulting in what is often referred to as the **Step-Builder-Pattern**. Before I show the code I want you to realize that our entire construction process of the kitchen could actually be described as a flowchart.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/njuf994qp0t474i3urx9.png)

All that the Step-Builder-Pattern does is translate this flowchart to code. Pretty straight forward once you think in terms of nodes (steps) and edges (functions).

```kotlin
interface FridgeStep {
    fun setFridge(fridge: Fridge) : PowerSocketStep
    fun build() : Kitchen
}

interface PowerSocketStep {
    fun setPowerSocket(powerSocket: PowerSocket) : SinkStep
}

interface BuildStep {
    fun build() : Kitchen
}

private class KitchenBuilder : FridgeStep, PowerSocketStep, BuildStep {
    var fridge: Fridge? = null
    var powerSocket: PowerSocket? = null

    override fun setFridge(fridge: Fridge) : PowerSocketStep {
        this.fridge = fridge
        return this
    }

    override fun setPowerSocket(powerSocket: PowerSocket) : SinkStep {
        this.powerSocket = powerSocket
        return this
    }

    override fun build(): Kitchen {
        return Kitchen(fridge, powerSocket, sink, waterPipe)
    }
}
```

As we can see we added a hierarchy of steps. Every step returns the next step. We can freely define when the user is allowed to call `.build()`. With this, there is no way for the user to create an invalid kitchen during coding. No surprises during runtime 🤗 yay. I strongly encourage you to check out this code (github repo is on top) into an IDE and go through the building process. You will see that there is basically no way a dev could mess this up by disregarding dependencies or calling build too early. It creates a really nice developer experience.

So we could stop here and live a happy and bugless life. Well not quite. Think about this: Your library grows because it provides such a nice developer experience and you want to add new rooms to it. Taking what you have learned you create a bathroom class and a drawer item. Now when you define the building steps for a bathroom you realize that kitchen and bathroom share three common steps: SinkStep, WaterPipeStep, and the new DrawerStep. Being the good programmer that you are you immediately think about refactoring this and outsource common steps into an abstract class "RoomBuilder". Do you already see where this approach will fail? If not try it in an IDE and see what happens.

In short, you cannot simply do that since the `.build()` function returns either a kitchen or a bathroom and all previous steps lead up to the specific build function. Meaning that you cannot share any function without returning the wrong type. Of course, you could just return a common supertype like "Any", but look at this potential code snippet:

```kotlin
class House() {
    fun addKitchen(kitchen: Kitchen) {
        this.kitchen = kitchen
    }
}
```

Here we cannot make the call `House.addKitchen(Kitchen.Builder().build())` if build returns "Any". How can we fix this?

There is one simple trick to tell the IDE what specific type a function returns during compile time without specifying it in the function itself. It's called generics. I guess most of you know what generics are so I won't go into too much detail, just think about them as a way to defer type-specifics to a later point in the development process.

```kotlin
abstract class RoomBuilder<T>: DrawerStep<T>, SinkStep<T>, WaterPipeStep<T>, BuildStep<T> where T : Room {
    var drawer: Drawer? = null
    var sink: Sink? = null
    var waterPipe: WaterPipe? = null

    override fun setDrawer(drawer: Drawer): SinkStep<T> {
        this.drawer = drawer
        return this
    }

    override fun setSink(sink: Sink): WaterPipeStep<T> {
        this.sink = sink
        return this
    }

    override fun setWaterPipe(waterPipe: WaterPipe): BuildStep<T> {
        this.waterPipe = waterPipe
        return this
    }
}

interface DrawerStep<T> {
    fun setDrawer(drawer: Drawer) : SinkStep<T>
    fun build() : T
}

interface SinkStep<T> {
    fun setSink(sink: Sink) : WaterPipeStep<T>
    fun build() : T
}

interface WaterPipeStep<T> {
    fun setWaterPipe(waterPipe: WaterPipe) : BuildStep<T>
}

interface BuildStep<T> {
    fun build() : T
}
```

As you can see the abstract RoomBuilder class itself does not care what the actual return type of `.build()` is. We simply pass it along when we extend it. Extending the KitchenBuilder from this class would look like this:

```kotlin
interface FridgeStep {
    fun setFridge(fridge: Fridge) : PowerSocketStep
    fun build() : Kitchen
}

interface PowerSocketStep {
    fun setPowerSocket(powerSocket: PowerSocket) : DrawerStep<Kitchen>
}

private class KitchenBuilder : RoomBuilder<Kitchen>(), FridgeStep, PowerSocketStep {
    var fridge: Fridge? = null
    var powerSocket: PowerSocket? = null

    override fun setFridge(fridge: Fridge): PowerSocketStep {
        this.fridge = fridge
        return this
    }

    override fun setPowerSocket(powerSocket: PowerSocket): DrawerStep<Kitchen> {
        this.powerSocket = powerSocket
        return this
    }

    override fun build(): Kitchen {
        return Kitchen(fridge, powerSocket, drawer, sink, waterPipe)
    }
}
```

Like this, every room that we add in the future simply passes itself as the generic type and therefore the IDE knows during compile-time what the returned type will be. No code duplication anymore 🤗 yay.


So by now we pretty much have most of OOP concepts covered: static objects, interfaces, abstract classes, and generics. So the single most important question remaining is: This really all worth it for just saving on a few constructors? The answer is like always: It depends. If you write code for internal usage only I would say just stick to the constructors. But if you write library code then the devs that will use your Builders are your customers, and creating a better user experience for your customers should always be the number 1 priority, even if this means that you have more effort internally.