Builder Design Pattern
The Builder is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation.
Builder design pattern is a creational design pattern and can be used to create complex object step-by-step. This pattern was introduced to solve some of the problems with Factory
and Abstract Factory
pattern when an object contains a lot of attributes or is complex in nature. Some very common examples of builder patterns are StringBuilder
and StringBuffer
.
There are two major issues with Factory and Abstract Factory design patterns:
- Too many arguments to pass from client program to the Factory class that can be error prone.
- Some of the parameters might be optional, but Factory pattern requires all parameters and optional parameters needs to be send as
null
.
Implementation
- First of all you need to create a static nested class and then copy all the argument from the outer class to the Builder class. We should follow naming convention, if the class name is Meal then builder is MealBuilder.
- Builder class should have public constructor with all required attribute as parameters.
- Builder class should have methods to set optional parameters and it should return the same builder class.
- The final step is to provide a build() method in the builder class that returns the instance of the class.
Below code contains the complex class Meal
and the corresponding inner builder class MealBuilder
.
public final class Meal {
private Burger burger;
private Soda soda;
private Fries fries;
private List<Ketcup> ketchups;
@Override
public String toString() {
return "Meal{" +
"burger=" + burger +
", soda=" + soda +
", fries=" + (fries != null ? fries : "NA") +
", ketchups=" + Arrays.toString(ketchups.toArray()) +
'}';
}
private Meal(MealBuilder builder) {
this.burger = builder.burger;
this.soda = builder.soda;
this.fries = builder.fries;
this.ketchups = builder.ketchups;
}
public static class MealBuilder {
// mandatory attributes for building a meal
private Burger burger;
private Soda soda;
// optional attributes in a meal
private Fries fries;
private List<Ketcup> ketchups;
// inorder to make a meal, a burger and a soda is must.
public MealBuilder(Burger burger, Soda soda) {
this.burger = burger;
this.soda = soda;
this.ketchups = new ArrayList<>();
}
// method to add fries to a meal.
public MealBuilder addFries(Fries fries) {
this.fries = fries;
return this;
}
// method to add ketchup to a meal.
public MealBuilder addKetcup(Ketcup ketcup) {
this.ketchups.add(ketcup);
return this;
}
// method to build the meal.
public Meal build() {
return new Meal(this);
}
}
}
This piece of code if used to test the above builder design pattern. It creates different representation of the same Meal class.
public class MealBuilderTest {
public static void main(String[] args) {
// 1. Standard meal with chicken burger and a diet coke.
MealBuilder builder = new Meal.MealBuilder(Burger.CHICKEN_BURGER, Soda.DIET_COKE);
Meal standardMeal = builder.build();
System.out.println("Standard Meal: " + standardMeal.toString());
// 2. Meal with vegetarian burger, pepsi and garlic fries.
builder = new Meal.MealBuilder(Burger.VEG_BURGER, Soda.PEPSI);
Meal heavyMeal = builder.addFries(Fries.GARLIC)
.build();
System.out.println("Heavy Meal: " + heavyMeal.toString());
// 3. Meal with vegetarian burger, coke, garlic fries and ketcups.
builder = new Meal.MealBuilder(Burger.VEG_BURGER, Soda.COKE);
Meal superMeal = builder.addFries(Fries.GARLIC)
.addKetcup(Ketcup.RANCH)
.addKetcup(Ketcup.TOMATO)
.build();
System.out.println("Super Meal: " + superMeal.toString());
}
}
Below is the output of the above main() method.
Standard Meal: Meal{burger=CHICKEN_BURGER, soda=DIET_COKE, fries=NA, ketchups=[]}
Heavy Meal: Meal{burger=VEG_BURGER, soda=PEPSI, fries=GARLIC, ketchups=[]}
Super Meal: Meal{burger=VEG_BURGER, soda=COKE, fries=GARLIC, ketchups=[RANCH, TOMATO]}
Source Code on Github