The Builder design pattern is a creational pattern that facilitates the construction of complex objects by separating their creation process from their representation. This approach allows for the step-by-step assembly of objects, enabling the creation of different representations using the same construction process.

Real-World Example:

Consider the process of assembling a computer. A customer can choose various components—such as the CPU, GPU, RAM, and storage—to build a customized system. The assembly process remains consistent, but the final product varies based on the selected components. The Builder pattern mirrors this scenario by allowing the construction of complex objects with varying configurations.

The Builder pattern provides a structured approach to construct complex objects step by step, enabling the creation of different representations or configurations of the object.

Example:

Let’s implement the Builder pattern in Scala to construct a Computer object with various optional components.

// Product
case class Computer(cpu: String,
                    gpu: Option[String],
                    ram: Int,
                    storage: Int,
                    hasWifi: Boolean,
                    hasBluetooth: Boolean)
 
// Builder
class ComputerBuilder {
  private var cpu: String = _
  private var gpu: Option[String] = None
  private var ram: Int = 8 // Default to 8GB
  private var storage: Int = 256 // Default to 256GB
  private var hasWifi: Boolean = false
  private var hasBluetooth: Boolean = false
 
  def setCpu(cpu: String): ComputerBuilder = {
    this.cpu = cpu
    this
  }
 
  def setGpu(gpu: String): ComputerBuilder = {
    this.gpu = Some(gpu)
    this
  }
 
  def setRam(ram: Int): ComputerBuilder = {
    this.ram = ram
    this
  }
 
  def setStorage(storage: Int): ComputerBuilder = {
    this.storage = storage
    this
  }
 
  def setWifi(hasWifi: Boolean): ComputerBuilder = {
    this.hasWifi = hasWifi
    this
  }
 
  def setBluetooth(hasBluetooth: Boolean): ComputerBuilder = {
    this.hasBluetooth = hasBluetooth
    this
  }
 
  def build(): Computer = {
    if (cpu == null || cpu.isEmpty) {
      throw new IllegalArgumentException("CPU must be specified")
    }
    Computer(cpu, gpu, ram, storage, hasWifi, hasBluetooth)
  }
}
 
// Usage
object BuilderPatternExample extends App {
  val customComputer = new ComputerBuilder()
    .setCpu("Intel i9")
    .setGpu("NVIDIA RTX 3080")
    .setRam(32)
    .setStorage(1024)
    .setWifi(true)
    .setBluetooth(true)
    .build()
 
  println(customComputer)
}

Output:

Computer(Intel i9,Some(NVIDIA RTX 3080),32,1024,true,true)

When to Use:

  • When the construction of an object is complex and involves multiple optional parameters.
  • When you want to create different representations of the same object.
  • When you need to ensure that an object is constructed in a specific sequence.

References: