024
creational

Builder

Reference Wikipedia ↗
Builder — class diagram
Plate 024 class diagram

The Builder pattern is a creational design pattern that lets you construct complex objects step-by-step. It allows customization of the object being built without making the construction process itself complex and unmanageable. The pattern separates the construction of a complex object from its representation, so the same construction process can create different representations.

This pattern is useful when an object has multiple optional attributes, or when the construction process is complex and involves many steps. It addresses the problems that can arise when using traditional constructors to create complex objects, particularly telescoping constructors and the need for a separate object for configuration. It promotes code reusability and maintainability by encapsulating the construction logic.

Usage

The Builder pattern is commonly used where:

  • Complex Object Creation: When constructing an object requires a sequence of steps and depends on various configuration options.
  • Varied Representations: When you need to create different versions or types of an object using the same construction process.
  • Avoiding Constructor Complexity: To avoid long and complicated constructors with numerous parameters.
  • Immutable Objects: When you want to construct immutable objects, as the builder can assemble the object’s parts before final creation.

Examples

  1. Java’s StringBuilder: The StringBuilder class in Java effectively implements the Builder pattern. You don’t construct a final string directly; instead, you use methods like append(), insert(), and delete() to build up the string incrementally. Finally, toString() creates the immutable String object. This avoids the inefficiencies of repeatedly creating new String objects during modification.

  2. Python’s datetime module: Constructing a datetime object in Python can be done directly with datetime(year, month, day, hour, minute, second). However, the datetime.datetime class also provides a builder-like interface through its various class methods (e.g., datetime.now(), datetime.fromtimestamp()). These methods allow you to create datetime objects with specific levels of detail, customizing the initialization process.

  3. Lombok @Builder Annotation (Java): The Lombok library provides the @Builder annotation which generates a builder class for you automatically. This simplifies the use of the Builder pattern substantially and is seen in many Spring Boot projects where complex DTOs are used.

Specimens

15 implementations
Specimen 024.01 Dart View specimen ↗

The Builder pattern is a creational design pattern that lets you construct complex objects step-by-step. It allows the same construction process to create different representations of the object. This is useful when an object has many optional parameters or complex dependencies.

The Dart implementation uses a dedicated Builder class with setter methods for each part of the object being built. A separate Director class (optional, but good practice) orchestrates the building process. The build() method in the builder returns the final constructed object. This approach is idiomatic Dart as it leverages classes and methods for structured object creation, and the fluent interface provided by the setters enhances readability. Immutability is encouraged by returning a new instance of the object in the build() method.

// Product class
class Computer {
  final String cpu;
  final String ram;
  final String storage;
  final String gpu;
  final String monitor;

  Computer({
    required this.cpu,
    required this.ram,
    required this.storage,
    this.gpu = 'Integrated',
    this.monitor = 'None',
  });

  @override
  String toString() {
    return 'Computer(cpu: $cpu, ram: $ram, storage: $storage, gpu: $gpu, monitor: $monitor)';
  }
}

// Builder class
class ComputerBuilder {
  String _cpu = '';
  String _ram = '';
  String _storage = '';
  String _gpu = 'Integrated';
  String _monitor = 'None';

  ComputerBuilder setCpu(String cpu) {
    _cpu = cpu;
    return this;
  }

  ComputerBuilder setRam(String ram) {
    _ram = ram;
    return this;
  }

  ComputerBuilder setStorage(String storage) {
    _storage = storage;
    return this;
  }

  ComputerBuilder setGpu(String gpu) {
    _gpu = gpu;
    return this;
  }

  ComputerBuilder setMonitor(String monitor) {
    _monitor = monitor;
    return this;
  }

  Computer build() {
    return Computer(
      cpu: _cpu,
      ram: _ram,
      storage: _storage,
      gpu: _gpu,
      monitor: _monitor,
    );
  }
}

// Director (optional)
class ComputerDirector {
  final ComputerBuilder builder;

  ComputerDirector(this.builder);

  void constructBasicComputer() {
    builder
      .setCpu('Intel i5')
      .setRam('8GB')
      .setStorage('512GB SSD');
  }

  void constructGamingComputer() {
    builder
      .setCpu('Intel i7')
      .setRam('16GB')
      .setStorage('1TB SSD')
      .setGpu('Nvidia RTX 3070')
      .setMonitor('27" 144Hz');
  }
}

void main() {
  ComputerDirector director = ComputerDirector(ComputerBuilder());

  director.constructBasicComputer();
  Computer basicComputer = director.builder.build();
  print('Basic Computer: $basicComputer');

  director.constructGamingComputer();
  Computer gamingComputer = director.builder.build();
  print('Gaming Computer: $gamingComputer');

  //Directly using the builder
  Computer anotherComputer = ComputerBuilder()
    .setCpu('AMD Ryzen 5')
    .setRam('12GB')
    .setStorage('1TB HDD')
    .build();
  print('Another Computer: $anotherComputer');
}