Skip to main content

Code example of the Builder Pattern example in Scala

The Builder Pattern is a creational design pattern for constructing complex objects following a Step-by-Step approach.

This pattern allows you to simplify the creation of complex objects that may require many fields and complex structures, or may have constructors with lots of parameters. It also allows you to decouple the construction of the class from its logic.

In this page, you’ll find an example of the Builder Pattern in Scala.

Output of the Builder

In order to use the Builder pattern, we need to define the class that we want to construct. In this example, we have created a class that could represent a character of an RPG game, so it may have lots of different attributes that need to be filled, like the name, level, base class (if it’s a warrior or a wizard), the weapons it carries… So you can imagine that the construction of this class can be quite complex and there can be lots of different options.

In this class we have defined some classic Getters and Setters following the standard class definition style guideline.

package wiki.dataengineering.designpatterns.builder

class Character {

private var _name: String = ""
private var _level: Int = 0
private var _weapon: String = ""
private var _baseClass: String = ""

// Setters and Getters
def name: String = _name
def name_= (name: String): Unit = { _name = name }

def level: Int = _level
def level_= (newValue: Int): Unit = { _level = newValue }

def baseClass: String = _baseClass
def baseClass_= (baseName: String): Unit = { _baseClass = baseName }

def weapon: String = _weapon
def weapon_= (weapon: String): Unit = { _weapon = weapon }

override def toString: String = {
s"""Character: ${name} - Base Class: ${baseClass} - Level ${level} - Current Weapon: ${weapon}"""
}

}

The builder

In order to use this pattern, we need a class that allow us to personalize the object that we want to create.

The main points of this class are:

  • We need a variable to hold the current state of the target object we're building. In this case, the Character class.
  • We need a “reset” method to clean the state of the class. This is needed in order to be able to build more than one instance from the builder.
  • Different methods for setting the values in of our target class. The important part here is that those methods need to return “this”. That means, the Builder class itself, with its current state. This will allow us to chain the methods and simplify its usage.
  • A Build() method that returns the final object, and resets the temporal target class instance.
package wiki.dataengineering.designpatterns.builder

class CharacterBuilder {

private var character: Character = null
this.reset()

/*
* Empties the character variable
*/
def reset(): Unit = {
this.character = new Character()
}

def setName(name: String): CharacterBuilder = {
this.character.name = name
this
}

def setBaseClass(baseClassName: String): CharacterBuilder = {
this.character.baseClass = baseClassName
this
}

def setLevel(level: Int): CharacterBuilder = {
this.character.level = level
this
}

def setWeapon(weapon: String): CharacterBuilder = {
this.character.weapon = weapon
this
}

/*
Returns the character instance with the properties setup
*/
def build(): Character = {
val tmp = this.character
this.reset()
tmp
}

}

How to use the Builder Pattern

Example of how to use this pattern for creating different instances of a complex class.

package wiki.dataengineering.designpatterns.builder

object Main {

def main(args: Array[String]): Unit = {

val char1 = new CharacterBuilder()
.setName("Name 1")
.setBaseClass("Warrior")
.setLevel(10)
.setWeapon("Basic Sword")
.build()
println(char1)

val char2 = new CharacterBuilder()
.setName("Name 2")
.setBaseClass("Archer")
.setLevel(15)
.setWeapon("Basic Bow")
.build()
println(char2)
}
}
--- Output
>> Character: Name 1 - Base Class: Warrior - Level 10 - Current Weapon: Basic Sword
>> Character: Name 2 - Base Class: Archer - Level 15 - Current Weapon: Basic Bow

You can see that instead of having a long constructor, we can easily chain the methods of the builder to create the object that we want.

Pattern variation: The director

An interesting variation/extension of this pattern is using a Director. It allows you to define templates that can be reused.

For example, when creating a new character, we can have different templates for some initial configurations.

This director will take an instance of the builder and define a set of steps that can be further parameterized.

package wiki.dataengineering.designpatterns.builder

class Director {

def buildBaseWizard(builder: CharacterBuilder, name: String): Unit = {
builder
.setName(name)
.setBaseClass("Wizard")
.setLevel(5)
.setWeapon("Staff")
}

def buildBaseWarrior(builder: CharacterBuilder, name: String): Unit = {
builder
.setName(name)
.setBaseClass("Warrior")
.setLevel(5)
.setWeapon("Sword")
}

}

How to use the Builder Director Pattern

Example of how to use the director pattern.

package wiki.dataengineering.designpatterns.builder

object Main {

def main(args: Array[String]): Unit = {

val dir = new Director()
val charBuilder = new CharacterBuilder()

dir.buildBaseWizard(charBuilder, "Name 3")
val char3 = charBuilder.build()
println(char3)

dir.buildBaseWarrior(charBuilder, "Name 4")
val char4 = charBuilder.build()
println(char4)

}
}
--- Output
>> Character: Name 3 - Base Class: Wizard - Level 5 - Current Weapon: Staff
>> Character: Name 4 - Base Class: Warrior - Level 5 - Current Weapon: Sword

This pattern can be extremely useful if you have defined your different pipelines through classes, and you want to provide an easy interface to configure them.