Builder design pattern in Java, Kotlin and Python Development 25.09.2016

Creational Design Patterns provide solution to instantiate an object in the best possible way for specific situations. Following design patterns come under this category.

Builder pattern allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object.

Builder pattern builds a complex object using simple objects and using a step by step approach.

It is mostly used when object can't be created in single step like in the de-serialization of a complex object.

dp_builder.png

The intent of the Builder Pattern is to separate the construction of a complex object from its representation, so that the same construction process can create different representations. This type of separation reduces the object size. The design turns out to be more modular with each implementation contained in a different builder object. Adding a new implementation (i.e., adding a new builder) becomes easier. The object construction process becomes independent of the components that make up the object. This provides more control over the object construction process.

The Builder pattern suggests using a dedicated object referred to as a Director, which is responsible for invoking different builder methods required for the construction of the final object. Different client objects can make use of the Director object to create the required object. Once the object is constructed, the client object can directly request from the builder the fully constructed object. To facilitate this process, a new method getResult can be declared in the common Builder interface to be implemented by different concrete builders.

The classes and objects participating in this pattern are:

  • Builder (VehicleBuilder)
    • specifies an abstract interface for creating parts of a Product object
  • ConcreteBuilder (MotorCycleBuilder, CarBuilder, ScooterBuilder)
    • constructs and assembles parts of the product by implementing the Builder interface
    • defines and keeps track of the representation it creates
    • provides an interface for retrieving the product
  • Director (Shop)
    • constructs an object using the Builder interface
  • Product (Vehicle)
    • represents the complex object under construction. ConcreteBuilder builds the product's internal representation and defines the process by which it's assembled
    • includes classes that define the constituent parts, including interfaces for assembling the parts into the final result

Advantage of Builder design pattern:

  • It provides clear separation between the construction and representation of an object.
  • It provides better control over construction process.
  • It supports to change the internal representation of objects.

Builder Pattern is used when:

  • The creation algorithm of a complex object is independent from the parts that actually compose the object.
  • The system needs to allow different representations for the objects that are being built.

Java

// represents the product created by the builder.
class Car {
    private String color;

    public Car() {}

    @Override
    public String toString() {
        return "Car [color=" + color + "]";
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

// the builder abstraction
interface CarBuilder {
    void setColor(String color);
    Car getResult();
}

class CarBuilderImpl implements CarBuilder {
    private Car car;

    public CarBuilderImpl() {
        car = new Car();
    }

    @Override
    public void setColor(String color) {
        car.setColor(color);
    }

    @Override
    public Car getResult() {
        return car;
    }
}

public class CarBuildDirector {
    private CarBuilder builder;

    public CarBuildDirector(CarBuilder builder) {
        this.builder = builder;
    }

    public Car construct() {
        builder.setColor("Red");
        return builder.getResult();
    }

    public static void main(String[] args) {
        CarBuilder builder = new CarBuilderImpl();
        CarBuildDirector carBuildDirector = new CarBuildDirector(builder);
        System.out.println(carBuildDirector.construct());
    }
}

Python 3

import abc

# represents the product created by the builder.
class Car:
    def __init__(self):
        self.color = None

    def get_color(self):
        return self.color

    def set_color(self, color):
        self.color = color

    def __str__(self):
        return "Car [color={0}]".format(self.color)


# the builder abstraction
class CarBuilder(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def set_color(self, color):
        pass

    @abc.abstractmethod
    def get_result(self):
        pass


class CarBuilderImpl(CarBuilder):
    def __init__(self):
        self.car = Car()

    def set_color(self, color):
        self.car.set_color(color)

    def get_result(self):
        return self.car


class CarBuildDirector:
    def __init__(self, builder):
        self.builder = builder

    def construct(self):
        self.builder.set_color("Red");
        return self.builder.get_result()

if __name__ == '__main__':
    builder = CarBuilderImpl()
    carBuildDirector = CarBuildDirector(builder)
    print(carBuildDirector.construct())

Kotlin

Like some other modern languages, Kotlin provides us with the ability to set default values for function parameters:

data class Mail(val to: String, 
    val title: String = "",
    val message: String = "",
    val cc: List<String> = listOf(), 
    val bcc: List<String> = listOf(), 
    val attachments: List<java.io.File> = listOf())

So, if you would like to send an email without CC, you can do it like that now:

val mail = Mail("one@recepient.org", "Hi", "How are you")

You can create a full-blown builder design pattern:

class MailBuilder(val to: String) { 
    private var mail: Mail = Mail(to)

    fun title(title: String): MailBuilder { 
        mail.title = title 
        return this 
    }
    // Repeated for other properties 

    fun build(): Mail { 
        return mail 
    } 
} 

You can use it to create your email in the following way:

val email = MailBuilder("hello@hello.com").title("What's up?").build()