Builder Design Pattern is a creational design pattern that separates the construction of complex objects from their representation, allowing the same construction process to create different representations. In this article, we will discuss the Builder Design Pattern and its implementation in Java.
Why Use Builder Design Pattern?
There are several reasons why you might want to use the Builder Design Pattern in your Java application. Some of the main reasons include:
Simplify Object Creation: The Builder Design Pattern can simplify the creation of complex objects by separating the construction process from the representation of the object.
Better Code Organization: By separating the construction process from the representation, the Builder Design Pattern can make code more organized and easier to read.
Encapsulate Object Creation: The Builder Design Pattern can encapsulate the object creation process, making it easier to modify the construction process in the future.
Increase Flexibility: The Builder Design Pattern can increase the flexibility of object creation, allowing the same construction process to create different representations.
Implementing Builder Design Pattern in Java
Let’s have a class Phone with 5 variables and we are also using a parameterized constructor to set the values. Along with this, we have a toString() method. When we print the object we get that method.
package BuilderDesign;
public class Phone {
private String os;
private String processor;
private int battery;
private double screensize;
private String company;
public Phone(String os, String processor, int battery, double screensize, String company) {
super();
this.os = os;
this.processor = processor;
this.battery = battery;
this.screensize = screensize;
this.company = company;
}
public String toString() {
return "Phone [os=" + os + ", processor=" + processor + ", battery=" + battery + ", screensize=" + screensize + ", company=" + company + "]";
}
}
Now we will create PhoneBuilder class, which is responsible for creating a phone. Here, we have all member variables with their setter function. Instead of using “void” in the setter function, we will use the “PhoneBuilder” object that means for any method we set the value you will get the object of PhoneBuilder.
package BuilderDesign;
/* RULES
* 1-> Create class and initialize variables
* 2-> Create Setter functions of that variables with datatype of class name(here PhoneBuilder) and return this.
* 3-> Create method similar to "toString" (here getPhone).
* 4---> Create class(here Phone) like in Encapsulation(with constructor(parameterized) and toString() method).
* 5---> Create a class used for testing the code(here Shop) and add details you want.
*/
public class PhoneBuilder {
private String os;
private String processor;
private int battery;
private double screensize;
private String company;
public PhoneBuilder setOs(String os) {
this.os = os;
return this;
}
public PhoneBuilder setProcessor(String processor) {
this.processor = processor;
return this;
}
public PhoneBuilder setBattery(int battery) {
this.battery = battery;
return this;
}
public PhoneBuilder setScreensize(double screensize) {
this.screensize = screensize;
return this;
}
public PhoneBuilder setCompany(String company) {
this.company = company;
return this;
}
public Phone getPhone() {
return new Phone(os, processor, battery, screensize, company);
}
}
Create the class Shop and set the values you want only & others will take the default value accordion to the datatype.
package BuilderDesign;
public class Shop {
public static void main(String[] args) {
Phone p1 = new PhoneBuilder().setCompany("Samsung").setBattery(6000).setOs("Android10").getPhone();
System.out.println(p1);
Phone p2 = new PhoneBuilder().setCompany("Htc").setOs("Android10").setScreensize(15.6).getPhone();
System.out.println(p2);
Phone p3 = new PhoneBuilder().setBattery(7000).setCompany("Samsung").setProcessor("Intel").getPhone();
System.out.println(p3);
}
}
OUTPUT
Phone [os=Android10, processor=null, battery=6000, screensize=0.0, company=Samsung]
Phone [os=Android10, processor=null, battery=0, screensize=15.6, company=Htc]
Phone [os=null, processor=Intel, battery=7000, screensize=0.0, company=Samsung]
Realtime case problem Implementation
Here is the real-time problem of any restaurant to calculate the cost of your meal. We have a few options, mentioned below:
Interface- Item, Packing
Abstract class- Burger, ColdDrink
Burger- VegBurger, ChickenBurger
Cold drink- Pepsi, Coke
Packing class- Bottle, Wrapper
and other classes
First, we have two interfaces, Item, and Packing:
package exampleBuilder;
public interface Item {
public String name();
public Packing packing();
public float price();
}
package exampleBuilder;
public interface Packing {
public String pack();
}
Then we have two abstract classes for types of food items that implement the interface item.
package exampleBuilder;
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
package exampleBuilder;
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float price();
}
For home delivery of food items, we need to wrap the food. For that purpose, we need a Wrapper class for Burger and Bottle class for Colddrink, and they are defined as,
package exampleBuilder;
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
package exampleBuilder;
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
Then, a Burger class is divided into two different types of burger as Veg and Non-Veg, which extends an abstract class, Burger.
package exampleBuilder;
public class VegBurger extends Burger {
@Override
public String name() {
return "VegBurger";
}
@Override
public float price() {
return 50.25 f;
}
}
package exampleBuilder;
public class ChickenBurger extends Burger {
@Override
public String name() {
return "ChickenBurger";
}
@Override
public float price() {
return 80.50 f;
}
}
Similarly, the class Colddrink is further divided into two different classes which extend an abstract class, ColdDrink.
package exampleBuilder;
public class Coke extends ColdDrink {
@Override
public String name() {
return "Coke";
}
@Override
public float price() {
return 40.0 f;
}
}
package exampleBuilder;
public class Pepsi extends ColdDrink {
@Override
public String name() {
return "Pepsi";
}
@Override
public float price() {
return 35.0 f;
}
}
After all these food items are known, we have to get a meal, shown by:
package exampleBuilder;
import java.util.ArrayList;
import java.util.List;
public class Meal {
List < Item > list = new ArrayList < Item > ();
public void addItem(Item item) {
list.add(item);
}
public float getCost() {
float cost = 0.0 f;
for (Item item: list) {
cost += item.price();
}
return cost;
}
public void showMeal() {
for (Item i: list) {
System.out.print("Item Name " + i.name());
System.out.print("\t Item Packing " + i.packing().pack());
System.out.println("\t Item Price " + i.price());
}
}
}
After the meal is ready, we need a MealBuilder class similar to a waiter taking your food order whether veg or non-veg meal. This is shown by:
package exampleBuilder;
public class MealBuilder {
public Meal vegMeal() {
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal nonvegMeal() {
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
At last, we just place our order and wait for the delicious food. Here the process is the same, except for the service time that the restaurant workers need to make your food ready. This is neglected here:
package exampleBuilder;
public class PlaceOrder {
public static void main(String[] args) {
MealBuilder mealbuilder = new MealBuilder();
Meal veg = mealbuilder.vegMeal();
veg.showMeal();
System.out.println("Meal Cost: " + veg.getCost());
Meal nonveg = mealbuilder.nonvegMeal();
nonveg.showMeal();
System.out.println("Meal Cost: " + nonveg.getCost());
}
}
OUTPUT
Item Name VegBurger Item Packing Wrapper Item Price 50.25
Item Name Coke Item Packing Bottle Item Price 40.0
Meal Cost: 90.25
Item Name ChickenBurger Item Packing Wrapper Item Price 80.5
Item Name Pepsi Item Packing Bottle Item Price 35.0
Meal Cost: 115.5
Advantages of Builder Design Pattern
Simplifies object creation
Improves code organization and readability
Encapsulates object creation
Increases flexibility
Reduces code duplication
Disadvantages of Builder Design Pattern
Adds complexity to code
May not be necessary for simple objects
Requires additional classes to be created
This can result in verbose code
Usage of Builder Design Pattern
Creating complex objects that have many properties
Improving code organization and readability
Encapsulating object creation logic
Providing a flexible way to create objects with different combinations of properties
Creating immutable objects with mandatory and optional fields
Providing a type-safe approach to object creation
Conclusion
The Builder Design Pattern is a useful design pattern in Java that can help to simplify the creation of complex objects by separating the construction process from their representation. This pattern can help to make code more organized, and easier to read, and increase the flexibility of object creation. In this article, we discussed the different approaches to implementing the Builder Design Pattern in Java, including using a static inner class and using a fluent interface. Both approaches are valid and can be used depending on the specific requirements of the project. However, it is important to keep in mind that the Builder Design Pattern should only be used when the object being built is complex and has many properties that need to be set. If the object being built is simple, using the Builder Design Pattern can add unnecessary complexity to the code. Overall, the Builder Design Pattern is a useful tool for creating complex objects and improving code organization in Java.
Comments