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
- Define the product, what structure and attributes it has.
- Create the builder interface, the interface declares the methods for building different parts of the product.
- Implement concrete builders that implement the builder abstraction. Each concrete builder should provide specific implementations for constructing the product.
- 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.
- 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.
Back to parent page: Creational Patterns
Design_Pattern Creational_Design_Patterns SOFT2201 Builder_Pattern Enum