Builder is a creational design pattern for constructing complex objects, it allows a same construction code to create objects with different representations. It is suitable for objects with large number of possible configurations and hard to create an object with many constructor parameters. Unlike other creational patterns, Builder can construct unrelated products, which don’t have common interface, you will see a demonstration in the Example section.

Applicability

  • When the object creation is complex, might involve many optional parameters and its cumbersome to provide numerous constructor overloads.
  • When you want to create different representations of some objects, the object’s attribute can have different possible configurations(for example, stone, iron, and wooden houses…).

Approach

  1. Define the product, what structure and attributes it has.
  2. Create the builder interface, the interface declares the methods for building different parts of the product.
  3. Implement concrete builders that implement the builder abstraction. Each concrete builder should provide specific implementations for constructing the product.
  4. Create director that uses a builder to construct the product. The director knows the order in which that parts of the product should be built and orchestrates the construction process.
  5. Client obtains the product from the director.

Components

  • Builder (abstract)
    • Specifies an abstract interface that defines a set of methods for creating the product. The methods correspond to the different properties of parts of the product.
  • Concrete builder
    • Constructs the product or part of the product step by step by implementing the builder methods.
    • Keeps track of the representation it creates.
    • Provides an interface (getResult) for retrieving the product.
  • Director (optional)
    • Defines the order in which to execute the building steps, while the builders provide the implementations for those steps.
  • Product
    • Represents the complex object under construction.
    • Concrete builder builds the product’s internal representation and define the process by which it’s assembled.

Example

The builder pattern is used to create instances of Meal with different configurations.

Identify builder pattern components

Product: Meal, MealReceipt Builder: MealBuilder Concrete builder: KidsMealBuilder, VeggieMealBuilder Director: MealDirector

Product

The product is Meal with different different compositions as attributes.

class Meal {
    private String burger;
    private String fries;
    private String drink;
    private String toy;
 
    public Meal(String burger, String fries, String drink, String toy) {
	    this.burger = burger;
	    this.fries = fries;
	    this.drink = drink;
	    this.toy = toy;
    }
    // getter methods for Meal properties
    // other behaviours of a meal
}

Here the MealReceipt is another product, it does not have the same ancestor as a Meal. They are not related.

class MealReceipt {
	private String burger;
	private String fries;
	private String drink;
	private String toy;
 
	public MealReceipt(String burger, String fries, String drink, String toy) {
		this.burger = burger;
		this.fries = fries;
		this.drink = drink;
		this.toy = toy;
	}
 
	public String printReceipt() {
		String receipt = "";
		receipt += "Burger choice: " + burger + "\n";
		receipt += "Fries choice: " + fries + "\n";
		receipt += "Drink choice: " + drink + "\n";
		if (this.toy != null) {
			receipt += "Toy choice: " + toy + "\n";
		} else {
			// veggie meal does not have a toy
			receipt += "Toy choice: N/A" + "\n";
		}
		return receipt;
	}
}

Builder

The builder is an interface that defines all common for building different parts of the Meal product.

interface Builder {
    MealBuilder setBurger(String burger);
    MealBuilder setFries(String fries);
    MealBuilder setDrink(String drink);
    MealBuilder setToy(String toy);
}

Concrete builder

The concrete builder implements the actions defined in the common interface.

class MealBuilder implements Builder {
    private String burger;
    private String fries;
    private String drink;
    private String toy;
 
    @Override
    public void setBurger(String burger) {
        this.burger = burger;
    }
 
    @Override
    public void setFries(String fries) {
        this.fries = fries;
    }
 
    @Override
    public void addDrink(String drink) {
        this.drink = drink;
    }
 
    @Override
    public void addToy(String toy) {
        this.toy = toy;
    }
 
    @Override
    public Meal getResult() {
        return new Meal(burger, fries, drink, toy);
    }
}

In this case we build the meal receipt for a meal, using the same steps as building a meal. This is a feature of builder pattern that it can construct unrelated products that does not belong to a same parent or of a same type.

class MealReceiptBuilder implements Builder {
    private String burger;
    private String fries;
    private String drink;
    private String toy;
 
    @Override
    public void setBurger(String burger) {
        this.burger = burger;
    }
 
    @Override
    public void setFries(String fries) {
        this.fries = fries;
    }
 
    @Override
    public void addDrink(String drink) {
        this.drink = drink;
    }
 
    @Override
    public void addToy(String toy) {
        this.toy = toy;
    }
 
    @Override
    public MealReceipt getResult() {
        return new MealReceipt(burger, fries, drink, toy);
    }
}

Director

The MealDirector is responsible for orchestrating the construction process. It guides the construction with a predefined sequence of steps. The director work with a builder object and may not know what product is being built (meal or receipt) as details are encapsulated.

class Director {
    public void createKidsMeal(Builder builder) { 
		builder.setBurger("Chicken Burger")
        builder.setFries("Small Fries")
        builder.setDrink("Soda")
        builder.setToy("Toy Car")
    }
 
	public void createVeggieMeal(Builder builder) {
		builder.setBurger("Veggie Burger")
        builder.setFries("Medium Fries")
        builder.setDrink("Juice")
	}
}

Client

Everything comes together at the client code.

public class Client {
    public static void main(String[] args) {
		Director director = new Director();
		MealBuilder builder = new MealBuilder();
		// director gets the concrete builder object from the client
		// as the client knows better which builder to use to get a 
		// specific product
		director.createKidsMeal(builder);
 
		Meal meal = builder.getResults();
		// now at this step, a kids meal is been built
 
		MealReceiptBuilder receiptBuilder = new MealReceiptBuilder();
		// we use the same director to create the receipt for kids meal
		director.createKidsMeal(receiptBuilder);
		MealReceipt mealReceipt = receiptBuilder.getResult();
		System.out.println(mealReceipt.printReceipt());
    }
}

In real-world implementation, you may use enum class to organise different configuration types instead of using string. enum provides an a concise way of communication and comes with enhanced memory save and performance.


Design_PatternCreational_Design_Patterns SOFT2201Builder_PatternEnum