Design Patterns: Builder pattern – with Kotlin and Kunafa Examples

As a part of our new design patterns series, I will talk about Builder pattern which is the second pattern in our reference book “Design Patterns: Elements of Reusable Object-Oriented Software” by the Gang of Four.

Builder pattern belongs to the creational patterns group. These patterns focus on creating objects with more flexible mechanisms .

Builder pattern used to “separate the construction of a complex object from its representation”, so if you need to create some complex objects with a different representation, and in the same time you want to make the creation process independent from operations that make up these objects, the builder pattern will be a good choice.

In order to get the final product, the builder pattern needs four main components Builder, Concrete Builder, Director and Product.

For example lets say you have a burger restaurant and each meal in the menu consists of a burger, a drink and a side dish. The cashier gets the order from the client and gives it to the concerned crew. This crew will collect the meal items and deliver it. Note that all the meals have the same contents but they differ in the representation. Also the cashier is not interested in how the crew will cook the order or how they will assemble it. He just focuses on directing orders to the crew and retrieves the final products.

This is exactly what happens in builder pattern, there is a director which controls all processes from a high level, builder that contains all common construction steps, and specialist concrete builders to produce products with different representation.

Builder: An interface which contains all possible constructions steps that used by concrete builders to generate different products.

Concrete Builder: Each different representation has a unique concrete builder, which implements the required steps methods to produce , assemble and return the final product.

Director: Director calls the required concrete builder’s methods to generate the final product.

Product: Define the final complex object properties.

The following diagram illustrates how the four components interact with each other.

Implementation:

To implement a builder pattern you need to define the four components and keep in mind that the builder interface must be general enough to construct products of all available representations.

To illustrate the implementation we will use the following example:

We will define a web page contains table views, each table has different theme, so we need a TableBuilder interface that contains abstract methods to build tables parts and we will assume these parts are header and body. Then we will use two different concrete builders (BlueBorderTableBuilder, ColourfulBorderTableBuilder) , each of them has its own implementation of TableBuilder’s methods to produce a different TableComponents.

Builder → TableBuilder

ConcreteBuilders → BlueBorderTableBuilder, ColourfulBorderTableBuilder

Director → TablesDirector

Product → TableComponent

Note: Kotlin language and Kunafa framework were used in the below code, if you are not familiar with Kunafa you can read our previous blogs Hello Kunafa! , Kunafa named styles and hash styles .

interface TableBuilder {
 fun buildHeader(columnsNumber: Int)
 fun buildBody(columnsNumber: Int, rowsNumber: Int)
 fun getTable(): TableComponent
}

class TablesDirector {
 fun constructTable(tableBuilder: TableBuilder) {
     tableBuilder.buildHeader(columnsNumber = 4)
     tableBuilder.buildBody(columnsNumber = 4, rowsNumber = 4)
     tableBuilder.getTable()
 }
}

class TableComponent : Component() {
    private var tableView: LinearLayout? = null
    private var headerView: View? = null
    private var bodyView: View? = null

    override fun onViewMounted(lifecycleOwner: LifecycleOwner) {
        super.onViewMounted(lifecycleOwner)
        tableView?.clearAllChildren()
        tableView?.apply {
            headerView?.let { mount(it) }
            bodyView?.let { mount(it) }
        }
    }

    override fun View?.getView(): View = verticalLayout {
        style {
            width = matchParent
            alignItems = Alignment.Center
            marginTop = 24.px
            backgroundColor = Color.white
            direction = "ltr"
        }
        tableView = verticalLayout {}
    }

    fun setHeader(header: View) {
        headerView = header
    }

    fun setBody(body: View) {
        bodyView = body
    }
}

class BlueBorderTable : TableBuilder {
    private val tableComponent = TableComponent()

    companion object {
        const val CELL_WIDTH = 120
    }

    override fun buildHeader(columnsNumber: Int) {
        tableComponent.setHeader(
                View().horizontalLayout {
                    style {
                        borderRight = "1px solid ${Color.blue}"
                        borderLeft = "1px solid ${Color.blue}"
                        borderTop = "1px solid ${Color.blue}"
                    }
                    for (column in 1..columnsNumber) {
                        textView {
                            style {
                                width = CELL_WIDTH.px
                                padding = 12.px
                                fontSize = 14.px
                                textAlign = TextAlign.Center
                                fontWeight = "bold"
                                borderRight = if (column != columnsNumber) "1px solid ${Color.blue}" else "none"
                            }
                            text = "Column $column"
                        }
                    }
                }
        )
    }

    override fun buildBody(columnsNumber: Int, rowsNumber: Int) {
        tableComponent.setBody(
                View().verticalLayout {
                    style {
                        borderTop = "1px solid ${Color.blue}"
                        borderRight = "1px solid ${Color.blue}"
                        borderLeft = "1px solid ${Color.blue}"
                        borderBottom = "1px solid ${Color.blue}"
                    }
                    for (row in 1..rowsNumber) {
                        horizontalLayout {
                            style {
                                borderBottom = if (row != rowsNumber) "1px solid ${Color.blue}" else "none"
                            }
                            for (column in 1..columnsNumber) {
                                textView {
                                    style {
                                        width = CELL_WIDTH.px
                                        padding = 12.px
                                        fontSize = 14.px
                                        textAlign = TextAlign.Center
                                        fontWeight = "bold"
                                        borderRight = if (column != columnsNumber) "1px solid ${Color.blue}" else "none"
                                    }
                                }
                            }
                        }
                    }
                }
        )
    }

    override fun getTable(): TableComponent {
        return tableComponent
    }
}

class ColourfulBorderTable : TableBuilder {
    private val tableComponent = TableComponent()

    companion object {
        const val CELL_WIDTH = 120
    }

    override fun buildHeader(columnsNumber: Int) {
        tableComponent.setHeader(
            View().horizontalLayout {
                style {
                    borderRight = "1px solid ${Color.blue}"
                    borderLeft = "1px solid ${Color.blue}"
                    borderTop = "1px solid ${Color.red}"
                }
                for (column in 1..columnsNumber) {
                    textView {
                        style {
                            width = CELL_WIDTH.px
                            padding = 12.px
                            fontSize = 14.px
                            textAlign = TextAlign.Center
                            fontWeight = "bold"
                            borderRight = if (column != columnsNumber) "1px solid ${Color.black}" else "none"
                        }
                        text = "Column $column"
                    }
                }
            }
        )
    }

    override fun buildBody(columnsNumber: Int, rowsNumber: Int) {
        tableComponent.setBody(
                View(). verticalLayout {
                    style {
                        borderTop = "1px solid ${Color("#ffaf50")}"
                        borderRight = "1px solid ${Color.blue}"
                        borderLeft = "1px solid ${Color.blue}"
                        borderBottom = "1px solid ${Color("#ffaf50")}"
                        backgroundColor = Color("ECF9F9")
                    }
                    for (row in 1..rowsNumber) {
                        horizontalLayout {
                            style {
                                borderBottom = if (row != rowsNumber) "1px solid ${Color.black}" else "none"
                            }
                            for (column in 1..columnsNumber) {
                                textView {
                                    style {
                                        width = CELL_WIDTH.px
                                        padding = 12.px
                                        fontSize = 14.px
                                        textAlign = TextAlign.Center
                                        fontWeight = "bold"
                                        borderRight = if (column != columnsNumber) "1px solid ${Color.black}" else "none"
                                    }
                                }
                            }
                        }
                    }
                }
        )
    }

    override fun getTable(): TableComponent {
        return tableComponent
    }
}

Then we can use our builder and director in the main() function to produce the requested table views

fun main() {
    val tablesDirector = TablesDirector()
    val blueBorderTableBuilder = BlueBorderTableBuilder()
    val colourfulBorderTableBuilder = ColourfulBorderTableBuilder()
    page {
        // Blue border table
        tablesDirector.constructTable(blueBorderTableBuilder)
        val blueTable = blueBorderTableBuilder.getTable()
        mount(blueTable)

        // colourful table
        tablesDirector.constructTable(colourfulBorderTableBuilder)
        val colourfulTable = colourfulBorderTableBuilder.getTable()
        mount(colourfulTable)
    }
}

Output:

Conclusion:

Builder pattern gives you the ability to separate construction and representation code, and allows you to build more complex objects with different representation and with more flexibility. Also we can use the same builder in different directors without need to change configurations.

On the other hand, builder pattern can increase the complexity of the code due to the multiple new classes declarations.

I will stop here but you can refer to the book to know about builder pattern’s known uses, related patterns and more details.

Leave a Reply

Your email address will not be published. Required fields are marked *