Builder Design Pattern Applied
Hi there!
Today i’m gonna share the first of a brand new design pattern series i made. The builder design pattern is a very useful and common pattern while developing serious apps. In this post i’ll give a tiny builder pattern framework, so you can always come back here and get it to work with.
A mnemonic, while dealing with builder pattern, is to think about customization. I always think about it, when i’m figuring out if i should use it or if i better take a factory. That’s the way it works better for me. try yourself.
The UML
Here is how the little framework looks like. Simple, nice and straightforward.
The code behind it
The code is also very simple, small, clean and self-explanatory. I like to code expressive, so i don’t need to comment a lot. In this sample here i did, because it has a tutorial character. While developing i created some convention to my self. I think it is very important to do so. It is like applying the right grammar on a language while developing.
For example: If i’m using the Builder Pattern, i always put the suffix Builder at the end. Well you may say or think now: what? But thats in fact a very, very, important info for beginners and expirienced developers. They will automatically see the idea behind it and will try not to break the pattern. In fact expirienced developers will love it and try to continue a good work, because they know, that the developer who wrote this, knows what he did and for sure there was a reason for it.
So always try to be clear enough and give the right information on the right places. Someone else will thank you later. But now to the code…
// 1. EXAMPLE: PARTS OF THE CUSTOMIZABLE PRODUCT WE WANT public interface Part { // DEFINE THE METHODS YOUR PARTS WILL HAVE... void anyMethodNameYouLike(); } // 2. THE BUILDER METHOD WILL ADD // PARTS RETURNING THE BUILDER ITSELF public interface BuildContract < B > { B mount(Part part); } // 3. DEFINE THE BUILDER'S CONTRUCTION METHOD // WHICH BUILDS AND RETURNS THE FINAL PRODUCT "T" public interface Builder < T > extends BuildContract < Builder < T > > { T build(); }
A real example
Nothing better then that to fix it and understand it better. Let´s implement a cake bakery. A colleague of yours wants to open a bakery and asked you to program a bakery’s software for him. Let’s do it..!
And by the way, I commend you heartily, using a UML diagram tool is as visualization mechanism to show your ideas and improve your design skills. Lets start by the UML:
The analogy
Let’s now use our tiny framework and make the analogy for our bakery. Ingredient is the Part, Recipe is the BuilderContract and Builder is the builder itself. Cake is the final, customizable product. CakeBuilder is the class which actually creates the product after customization (after the addition of as many parts – ingredients – as you want). The client would be the final client or your colleague taking the order. Just use or imagination… Let’s dive into code now…
The Ingredients (Parts)
The parts in our example are the ingredients. Let’s implement some ingredients to use it later to make a cake.
// 1. EXAMPLE: PART TO CUSTOMIZATE "INGREDIENTS" public interface Ingredient { // INGREDIENTS WILL HAVE... void printName(); String getUnitPrice(); void printCalories(); }
public class LightMilk implements Ingredient { private int deciLiter; private int calories; private String unitPrice; public LightMilk(int deciLiter){this.deciLiter=deciLiter;} public LightMilk(int deciLiter, int calories, String unitPrice) { super(); this.deciLiter = deciLiter; this.calories = calories; this.unitPrice = unitPrice; } @Override public void printName() {System.out.printf(" Light Milk");} @Override public String getUnitPrice() {return unitPrice;} @Override public void printCalories() {System.out.printf(" 76kc");} public int getDeciLiter() {return deciLiter;} public void setDeciLiter(int deciLiter) {this.deciLiter = deciLiter;} public int getCalories() {return calories;} public void setCalories(int calories) {this.calories = calories;} public void setUnitPrice(String unitPrice) {this.unitPrice = unitPrice;} } public class Sugar implements Ingredient { private int gram; private int calories; private String unitPrice; public Sugar(int deciLiter){this.gram=deciLiter;} public Sugar(int gram, int calories, String unitPrice) { super(); this.gram = gram; this.calories = calories; this.unitPrice = unitPrice; } @Override public void printName() {System.out.printf(" Sugar");} @Override public String getUnitPrice() {return unitPrice;} @Override public void printCalories() {System.out.printf(" 40kc");} public int getGram() {return gram;} public void setGram(int gram) {this.gram = gram;} public int getCalories() {return calories;} public void setCalories(int calories) {this.calories = calories;} public void setUnitPrice(String unitPrice) {this.unitPrice = unitPrice;} } public class Choco implements Ingredient { private int gram; private int calories; private String unitPrice; public Choco(int gram, int calories, String unitPrice) { super(); this.gram = gram; this.calories = calories; this.unitPrice = unitPrice; } public int getGram() {return gram;} public void setGram(int gram) {this.gram = gram;} public int getCalories() {return calories;} public void setCalories(int calories) {this.calories = calories;} public void setUnitPrice(String unitPrice) {this.unitPrice = unitPrice;} @Override public void printName() {System.out.printf(" Chocolate");} @Override public void printCalories() {System.out.printf(" 389kc");} @Override public String getUnitPrice() {return unitPrice;} } public class NoSugar implements Ingredient { private int gram; private int calories; private String unitPrice; public NoSugar(int deciLiter){this.gram=deciLiter;} public NoSugar(int gram, int calories, String unitPrice) { super(); this.gram = gram; this.calories = calories; this.unitPrice = unitPrice; } @Override public void printName() {System.out.printf(" No Sugar");} @Override public String getUnitPrice() {return unitPrice;} @Override public void printCalories() {System.out.printf(" 0kc");} public int getGram() {return gram;} public void setGram(int gram) {this.gram = gram;} public int getCalories() {return calories;} public void setCalories(int calories) {this.calories = calories;} public void setUnitPrice(String unitPrice) {this.unitPrice = unitPrice;} } public class Milk implements Ingredient { private int deciLiter; private int calories; private String unitPrice; public Milk(int deciLiter){this.deciLiter=deciLiter;} public Milk(int deciLiter, int calories, String unitPrice) { super(); this.deciLiter = deciLiter; this.calories = calories; this.unitPrice = unitPrice; } @Override public void printName() {System.out.printf(" Milk");} @Override public String getUnitPrice() {return unitPrice;} @Override public void printCalories() {System.out.printf(" 128kc");} public int getDeciLiter() {return deciLiter;} public void setDeciLiter(int deciLiter) {this.deciLiter = deciLiter;} public int getCalories() {return calories;} public void setCalories(int calories) {this.calories = calories;} public void setUnitPrice(String unitPrice) {this.unitPrice = unitPrice;} }
The Builder’s Contract
This is the Recipe in our example.
// 2. THE BUILDER METHOD WILL ADD // INGREDIENTS RETURNING THE BUILDER ITSELF public interface Recipe < B > { B addIngredient(Ingredient ingredient); } // 3. DEFINE THE BUILDER CONTRUCTION METHOD // WHICH BUILDS AND RETURNS THE FINAL PRODUCT "T" public interface Builder < T > extends Recipe < Builder < T > > { T build(); } import java.util.ArrayList; import java.util.List; // 4. IMPLEMENT THE BUILDER ACC. TO YOUR NEEDS public class CakeBuilder implements Builder < Cake > { // IN THIS CASE THE PARTS ARE THE INGREDIENTS private List < Ingredient > ingredients=new ArrayList < Ingredient > ( ); @Override public Cake build() { if(!ingredients.isEmpty()){ // THE FINAL PRODUCT IS A CHOCO-MUFFIN return new Cake(ingredients); } return new Cake(null); } @Override // BECAUSE I ALWAYS GET A BUILDER BACK, I'M ABLE TO // ADD A LOT OF PARTS BEFORE I CALL "BUILD()" public Builder < Cake > addIngredient(Ingredient ingredient) { if(ingredient!=null){ ingredients.add(ingredient); } return this; } }
The product
In our example the product to build is a cake.
import java.util.List; public class Cake { public Cake(List < Ingredient > ingredients){ String muffin = ""; if(ingredients==null){ System.out.println(" zero cake "+muffin); return; } // PRINT OUT MUFFIN INGREDIENTS System.out.printf(" Cake with: "); for (Ingredient ingredient : ingredients) { ingredient.printName(); } // PRINT OUT PART PRICES for (Ingredient ingredient : ingredients) { muffin+=" "+ingredient.getUnitPrice();//NOPMD } System.out.println(" - Price: "+muffin); } public void printResult(){ System.out.println(" Cake is ready!"); } }
Testing it
Finally the client test. Here we can see the usage of it:
// 5. TESTING THE CHOCO-BUILDER public class Client { public static void main(String[] args) { Builder < Cake > chocoMuffinBuilder = new CakeBuilder(); chocoMuffinBuilder.addIngredient(new Choco(10, 23, "3.39")); chocoMuffinBuilder.addIngredient(new Milk(34, 67, "1.57")); chocoMuffinBuilder.addIngredient(new Sugar(34, 67, "2.00")); final Cake chocoMuffin = chocoMuffinBuilder.build(); chocoMuffin.printResult(); } }
That’s all! Hope you like it!
Reference: | Builder Design Pattern Applied from our JCG partner Ricardo Ferreira at the Clean Code Development – Quality Seal blog. |
Hi Ricardo, I really liked your implementation of Builder pattern. It seems, in terms of implementation, there could be multiple variations of same patterns, or we may say multiple flavours (the word “flavours” suits more considering your CakeBuilder :) ). But i am not sure about your basic idea of builder, you see builder pattern as a Customizable or Assembled unit, which you have very well implemented. Where as i see builder pattern as a template based development of a complex product (object). In concrete terms the Recipe object in your CakeBuilder example must hold the steps for cake development… Read more »