Builder Pattern
But why?
Imagine the headache constructing complex objects from constructor that has more than 5 arguments.
Then you need to remember what is what & you explicitly need to send null
for any property that you do not want to set.
Take for example below snippet:
// The complex entity
public class IceCream {
private int scoops;
private Type type;
private Flavour flavour;
private Brand brand;
private float price;
// a big exhaustive constructor
public IceCream(int scoops, Type type,
Flavour flavour, Brand brand, float price) {
// the usual code here
}
}
// Using the above complex entity
public class IceCreamMachine {
public IceCream orderAnIceCream() {
return new IceCream(1, CUP, MANGO, BASKINS, 125);
}
}
Builder Pattern
Builder Pattern addresses this problem statement by following a step-by-step approach. Essentially it creates the entire object from small steps.
This comes handy when we have a complex object & a constructor does not help with being informative.
How its done?
The implementation is simple. We just create a separate class that provides steps (methods) to set individual properties with ease. Finally it will return the desired object.
- Create your entity as normal but for the constructor make it private & let it accept a single argument as builder
public class IceCream { private int scoops; private Type type; private Flavour flavour; private Brand brand; private float price; public IceCream(Builder builder) { this.scoops = builder.scoops; this.type = builder.type; this.flavour = builder.flavour; this.brand = builder.brand; this.price = builder.price; } }
- Create a
static
inner class as builder - The builder is required to have same properties as your entity
- The builder then also provide methods for setting each property & return itself
- Below is a snippet for builder
public class IceCream { // leaving out the fields just for now public static class Builder { // same properties as IceCream entity private int scoops; private Type type; private Flavour flavour; private Brand brand; private float price; // keeping a default constructor if we need // to set all properties one by one private Builder() { super(); } // parameterized constructor to create // builder with bare-minimum properties private Builder(Flavour flavour, Brand brand) { this.brand = brand; this.flavour = flavour; } public Builder addScoops(int scoops) { this.scoops = this.scoops + scoops; return this; } public Builder selectType(Type type) { this.type = type; return this; } public Builder selectFlavour(Flavour flavour) { this.flavour = flavour; return this; } public Builder selectBrand(Brand brand) { this.brand = brand; return this; } public Builder calculatePrice() { this.price = (this.brand.getRate() + this.type.getRate() + this.flavour.getRate()) * this.scoops; return this; } // method invokes the parent class constructor public IceCream build() { return new IceCream(this); } // static method for easy object creation public static Builder createInstance(Flavour flavour, Brand brand) { return new Builder(flavour, brand); } // static method for easy object creation public static Builder createInstance() { return new Builder(); } } }
- Below snippet shows how to construct objects using this builder
// using builder with parameters IceCream vanillaSmall = Builder.createInstance(Flavour.VANILLA, Brand.BASKINS) .addScoops(1) .selectType(Type.CUP) .calculatePrice() .build(); // using builder with all steps explicitly defined IceCream mangoLarge = Builder.createInstance() .selectFlavour(Flavour.MANGO) .selectBrand(Brand.KWALITY) .addScoops(3) .selectType(Type.CUP) .calculatePrice() .build();
- The way we now initialize objects is very clean & readable.
Caveats?
Yes like everything else, we need to maintain the builder with same properties which is cumbersome.