Builder Design Pattern In Java
Join the DZone community and get the full member experience.
Join For FreeHere I am with my another article on design patterns - Builder Design Pattern. A very useful creational design pattern that lets us construct complex objects step by step.
Builder Design Pattern
- The Builder design pattern is designed to provide a flexible solution to various object creation problems in object-oriented programming.
- The Builder design pattern provides a way to separate the construction of a complex object from its representation.
- The Builder pattern constructs a complex object by using simple objects and step by step approach.
- The pattern provides one of the best ways to create a complex object.
- It is one of the Gang of Four design patterns that describe how to solve recurring design problems in object-oriented software.
- This pattern is useful to build different immutable objects using same object building process.
The builder pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. The construction is controlled by a director object that only needs to know the type of object it is to create.
So the important components of Builder Design Pattern are as below:
- Product — The Class which defines the complex object we are trying to build step by step using simple objects.
- Builder — The abstract class/interface which defines all the steps required to perform to produce the complex Product object. Generally, each step is declared here (abstract) since the implementation comes under the concrete builder class.
- ConcreteBuilder — The Builder class which provides the actual code to build the Product object. We can have number of different ConcreteBuilder classes each one giving a different flavor or way of producing the Product object.
- Director — The class under which supervision builder performs the ordered steps to build the Product object. The Director normally receives the builder to perform the steps in correct order to build the Product object.
The Builder design pattern solves problems like:
- How can a class (the same construction process) create different representations of a complex object?
- How can a class that includes creating a complex object be simplified?
Let's implements a Car Manufacturing Example using Builder Design Pattern.
Car Manufacturing Example Using Builder Design Pattern
Step 1: Create Car class which is Product in our example:
xxxxxxxxxx
package org.trishinfotech.builder;
public class Car {
private String chassis;
private String body;
private String paint;
private String interior;
public Car() {
super();
}
public Car(String chassis, String body, String paint, String interior) {
this();
this.chassis = chassis;
this.body = body;
this.paint = paint;
this.interior = interior;
}
public String getChassis() {
return chassis;
}
public void setChassis(String chassis) {
this.chassis = chassis;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getPaint() {
return paint;
}
public void setPaint(String paint) {
this.paint = paint;
}
public String getInterior() {
return interior;
}
public void setInterior(String interior) {
this.interior = interior;
}
public boolean doQualityCheck() {
return (chassis != null && !chassis.trim().isEmpty()) && (body != null && !body.trim().isEmpty())
&& (paint != null && !paint.trim().isEmpty()) && (interior != null && !interior.trim().isEmpty());
}
public String toString() {
// StringBuilder class also uses Builder Design Pattern with implementation of java.lang.Appendable interface
StringBuilder builder = new StringBuilder();
builder.append("Car [chassis=").append(chassis).append(", body=").append(body).append(", paint=").append(paint)
.append(", interior=").append(interior).append("]");
return builder.toString();
}
}
Please note here that I have added a validation method doQualityCheck
in the class. I believe Builder
should not produce any incomplete or invalid Product
object. So this method will help us in validating the car manufacturing.
Step 2: Create the CarBuilder
abstract class/interface to define all of the required steps to build the Car.
xxxxxxxxxx
package org.trishinfotech.builder;
public interface CarBuilder {
// Step 1
public CarBuilder fixChassis();
// Step 2
public CarBuilder fixBody();
// Step 3
public CarBuilder paint();
// Step 4
public CarBuilder fixInterior();
// delivery of car
public Car build();
}
Please notice that I made the return type CarBuilder
as return type of all steps created here. This will help us in steps calling in a chained way. Here, one very important method build which is to get the result or create final object of Car. This method will actually checks the validity of the car and delivers (returns) it only if its manufacturing is complete (valid).
Step 3: Now it's time to write ConcreteBuilder
. As I mentioned that we can have different flavors of ConcreteBuilder
and each one performs its manufacturing in its own way to provide different representations on the Complex Car
object.
So, here we have code of ClassicCarBuilder
which manufacture old car model.
x
package org.trishinfotech.builder;
public class ClassicCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public ClassicCarBuilder() {
super();
}
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the classical model");
this.chassis = "Classic Chassis";
return this;
}
public CarBuilder fixBody() {
System.out.println("Assembling body of the classical model");
this.body = "Classic Body";
return this;
}
public CarBuilder paint() {
System.out.println("Painting body of the classical model");
this.paint = "Classic White Paint";
return this;
}
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the classical model");
this.interior = "Classic interior";
return this;
}
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
Now another builder ModernCarBuilder
to build a latest car model.
x
package org.trishinfotech.builder;
public class ModernCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public ModernCarBuilder() {
super();
}
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the modern model");
this.chassis = "Modern Chassis";
return this;
}
public CarBuilder fixBody() {
System.out.println("Assembling body of the modern model");
this.body = "Modern Body";
return this;
}
public CarBuilder paint() {
System.out.println("Painting body of the modern model");
this.paint = "Modern Black Paint";
return this;
}
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the modern model");
this.interior = "Modern interior";
return this;
}
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
And another SportsCarBuilder
to build a sports car.
xxxxxxxxxx
package org.trishinfotech.builder;
public class SportsCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public SportsCarBuilder() {
super();
}
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the sports model");
this.chassis = "Sporty Chassis";
return this;
}
public CarBuilder fixBody() {
System.out.println("Assembling body of the sports model");
this.body = "Sporty Body";
return this;
}
public CarBuilder paint() {
System.out.println("Painting body of the sports model");
this.paint = "Sporty Torch Red Paint";
return this;
}
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the sports model");
this.interior = "Sporty interior";
return this;
}
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
Step 4: Now we will write a Director class call AutomotiveEngineer
under which supervision the builder will build the Car in step by step in correct order.
xxxxxxxxxx
package org.trishinfotech.builder;
public class AutomotiveEngineer {
private CarBuilder builder;
public AutomotiveEngineer(CarBuilder builder) {
super();
this.builder = builder;
if (this.builder == null) {
throw new IllegalArgumentException("Automotive Engineer can't work without Car Builder!");
}
}
public Car manufactureCar() {
return builder.fixChassis().fixBody().paint().fixInterior().build();
}
}
We can see that the manufactureCar
method is calling the car building steps in correct order.
Now, it's time to write a Main class to execute and test the output.
xxxxxxxxxx
package org.trishinfotech.builder;
public class Main {
public static void main(String[] args) {
CarBuilder builder = new SportsCarBuilder();
AutomotiveEngineer engineer = new AutomotiveEngineer(builder);
Car car = engineer.manufactureCar();
if (car != null) {
System.out.println("Below car delievered: ");
System.out.println("======================================================================");
System.out.println(car);
System.out.println("======================================================================");
}
}
}
Below is the output of the program:
xxxxxxxxxx
Assembling chassis of the sports model
Assembling body of the sports model
Painting body of the sports model
Setting up interior of the sports model
Below car delievered:
======================================================================
Car [chassis=Sporty Chassis, body=Sporty Body, paint=Sporty Torch Red Paint, interior=Sporty interior]
======================================================================
I hope we are good with the explanation and example to understand the Builder
Pattern. Few of us also find this matching with Abstract Factory Pattern which I have covered in my another article. The main difference between Builder Pattern and Abstract Factory Pattern is that, Builder provides us more or better control over the object creation process. In short Abstract Factory Pattern deals with "WHAT" and Builder Pattern deals with "HOW".
The Builder Design Pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. The construction is controlled by a director object that only needs to know the type of object it is to create.
Source Code can be found here: Real-Builder-Design-Pattern-Source-Code
I found builder pattern very useful and one of the most commonly used in the applications nowadays. I found Builder more useful while dealing with the immutable objects. We all know how much good immutable objects and the use of immutable objects is getting increased day by day, especially after release of Java 8.
I use Builder for writing my immutable complex classes and I like to present the idea here.
As an Example, We have an Employee
class in which we have number of fields.
xxxxxxxxxx
public class Employee {
private int empNo;
private String name;
private String depttName;
private int salary;
private int mgrEmpNo;
private String projectName;
}
Suppose only 2 fields EmpNo
and EmpName
are mandatory, and the rest all are optional. Since it's an immutable class, I have 2 choice while writing constructors.
- Write a constructor with the parameters for all the fields.
- Write multiple constructors for different combination of parameters to create different representations of Employee object.
I thought option 1 is not good since I don't like to have more than 3-4 parameters in a method. That's not look good and it even worst when many of parameters are null or zero.
xxxxxxxxxx
Employee emp1 = new Employee (100, "Brijesh", null, 0, 0, "Builder Pattern");
Option 2 is also not very good since we end up creating too many constructors.
xxxxxxxxxx
public Employee(int empNo, String name) {
super();
if (empNo <= 0) {
throw new IllegalArgumentException("Please provide valid employee number.");
}
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Please provide employee name.");
}
this.empNo = empNo;
this.name = name;
}
public Employee(int empNo, String name, String depttName) {
this(empNo, name);
this.depttName = depttName;
}
public Employee(int empNo, String name, String depttName, int salary) {
this(empNo, name, depttName);
this.salary = salary;
}
public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo) {
this(empNo, name, depttName, salary);
this.mgrEmpNo = mgrEmpNo;
}
public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo, String projectName) {
this(empNo, name, depttName, salary, mgrEmpNo);
this.projectName = projectName;
}
So, here is the solution with the help of Builder Pattern:
xxxxxxxxxx
package org.trishinfotech.builder.example;
public class Employee {
private int empNo;
private String name;
private String depttName;
private int salary;
private int mgrEmpNo;
private String projectName;
public Employee(EmployeeBuilder employeeBuilder) {
if (employeeBuilder == null) {
throw new IllegalArgumentException("Please provide employee builder to build employee object.");
}
if (employeeBuilder.empNo <= 0) {
throw new IllegalArgumentException("Please provide valid employee number.");
}
if (employeeBuilder.name == null || employeeBuilder.name.trim().isEmpty()) {
throw new IllegalArgumentException("Please provide employee name.");
}
this.empNo = employeeBuilder.empNo;
this.name = employeeBuilder.name;
this.depttName = employeeBuilder.depttName;
this.salary = employeeBuilder.salary;
this.mgrEmpNo = employeeBuilder.mgrEmpNo;
this.projectName = employeeBuilder.projectName;
}
public int getEmpNo() {
return empNo;
}
public String getName() {
return name;
}
public String getDepttName() {
return depttName;
}
public int getSalary() {
return salary;
}
public int getMgrEmpNo() {
return mgrEmpNo;
}
public String getProjectName() {
return projectName;
}
public String toString() {
// StringBuilder class also uses Builder Design Pattern with implementation of
// java.lang.Appendable interface
StringBuilder builder = new StringBuilder();
builder.append("Employee [empNo=").append(empNo).append(", name=").append(name).append(", depttName=")
.append(depttName).append(", salary=").append(salary).append(", mgrEmpNo=").append(mgrEmpNo)
.append(", projectName=").append(projectName).append("]");
return builder.toString();
}
public static class EmployeeBuilder {
private int empNo;
protected String name;
protected String depttName;
protected int salary;
protected int mgrEmpNo;
protected String projectName;
public EmployeeBuilder() {
super();
}
public EmployeeBuilder empNo(int empNo) {
this.empNo = empNo;
return this;
}
public EmployeeBuilder name(String name) {
this.name = name;
return this;
}
public EmployeeBuilder depttName(String depttName) {
this.depttName = depttName;
return this;
}
public EmployeeBuilder salary(int salary) {
this.salary = salary;
return this;
}
public EmployeeBuilder mgrEmpNo(int mgrEmpNo) {
this.mgrEmpNo = mgrEmpNo;
return this;
}
public EmployeeBuilder projectName(String projectName) {
this.projectName = projectName;
return this;
}
public Employee build() {
Employee emp = null;
if (validateEmployee()) {
emp = new Employee(this);
} else {
System.out.println("Sorry! Employee objects can't be build without required details");
}
return emp;
}
private boolean validateEmployee() {
return (empNo > 0 && name != null && !name.trim().isEmpty());
}
}
}
I wrote EmployeeBuilder
as a public static nested class. You can write as normal public class in a separate Java file. There is not much difference in both.
Now a EmployeeMain
program to execute and create an Employee
object:
xxxxxxxxxx
package org.trishinfotech.builder.example;
public class EmployeeMain {
public static void main(String[] args) {
Employee emp1 = new Employee.EmployeeBuilder().empNo(100).name("Brijesh").projectName("Builder Pattern")
.build();
System.out.println(emp1);
}
}
I hope you like the idea. We can use this even in creating more complex objects. I did not wrote Director here, since all steps (collecting values for fields) are not compulsory and can be done in any order. Just to make sure, I am creating an Employee
object only after getting all the mandatory fields, I wrote a validation method.
Restaurant Order Placement Example With Builder Pattern
I am sharing code for a Restaurant Order Placement where Order is immutable object and requires Order Service Type (Take Away/Eat Here), all the Food Items we want, and the Customer Name(optional) at the time of placing the order. Food Items can be as many as we want. So, here is the code for the example.
Code for OrderService
enum:
xxxxxxxxxx
package org.trishinfotech.builder;
public enum OrderService {
TAKE_AWAY("Take Away", 2.0d), EAT_HERE("Eat Here", 5.5d);
private String name;
private double tax;
OrderService(String name, double tax) {
this.name = name;
this.tax = tax;
}
public String getName() {
return name;
}
public double getTax() {
return tax;
}
}
Code for FoodItem
interface:
xxxxxxxxxx
package org.trishinfotech.builder.meal;
import org.trishinfotech.builder.packing.Packing;
public interface FoodItem {
public String name();
public int calories();
public Packing packing();
public double price();
}
Code for Meal
class. The Meal
class offers predefined food-items with discount on item-price (not on packing-price).
xxxxxxxxxx
package org.trishinfotech.builder.meal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.trishinfotech.builder.packing.MultiPack;
import org.trishinfotech.builder.packing.Packing;
public class Meal implements FoodItem {
private List<FoodItem> foodItems = new ArrayList<FoodItem>();
private String mealName;
private double discount;
public Meal(String mealName, List<FoodItem> foodItems, double discount) {
super();
if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) {
throw new IllegalArgumentException(
"Meal can't be order without any food item");
}
this.mealName = mealName;
this.foodItems = new ArrayList<FoodItem>(foodItems);
this.discount = discount;
}
public List<FoodItem> getFoodItems() {
return foodItems;
}
public String name() {
return mealName;
}
public int calories() {
int totalCalories = foodItems.stream().mapToInt(foodItem -> foodItem.calories()).sum();
return totalCalories;
}
public Packing packing() {
double packingPrice = foodItems.stream().map(foodItem -> foodItem.packing())
.mapToDouble(packing -> packing.packingPrice()).sum();
return new MultiPack(packingPrice);
}
public double price() {
double totalPrice = foodItems.stream().mapToDouble(foodItem -> foodItem.price()).sum();
return totalPrice;
}
public double discount() {
return discount;
}
}
Food:
Code for Burger
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.burger;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.packing.Packing;
import org.trishinfotech.builder.packing.Wrap;
public abstract class Burger implements FoodItem {
public Packing packing() {
return new Wrap();
}
}
Code for ChickenBurger
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.burger;
public class ChickenBurger extends Burger {
public String name() {
return "Chicken Burger";
}
public int calories() {
return 300;
}
public double price() {
return 4.5d;
}
}
Code for VegBurger
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.burger;
public class VegBurger extends Burger {
public String name() {
return "Veg Burger";
}
public int calories() {
return 180;
}
public double price() {
return 2.7d;
}
}
Code for Nuggets
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.nuggets;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.packing.Container;
import org.trishinfotech.builder.packing.Packing;
public abstract class Nuggets implements FoodItem {
public Packing packing() {
return new Container();
}
}
Code for CheeseNuggets
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.nuggets;
public class CheeseNuggets extends Nuggets {
public String name() {
return "Cheese Nuggets";
}
public int calories() {
return 330;
}
public double price() {
return 3.8d;
}
}
Code for ChickenNuggets
class:
xxxxxxxxxx
package org.trishinfotech.builder.food.nuggets;
public class ChickenNuggets extends Nuggets {
public String name() {
return "Chicken Nuggets";
}
public int calories() {
return 450;
}
public double price() {
return 5.0d;
}
}
Beverages:
Beverages comes in a size. So, here is the code for BeverageSize
enum:
xxxxxxxxxx
package org.trishinfotech.builder.beverages;
public enum BeverageSize {
XS("Extra Small", 110), S("Small", 150), M("Medium", 210), L("Large", 290);
private String name;
private int calories;
BeverageSize(String name, int calories) {
this.name = name;
this.calories = calories;
}
public String getName() {
return name;
}
public int getCalories() {
return calories;
}
}
Code for Drink
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages;
import org.trishinfotech.builder.meal.FoodItem;
public abstract class Drink implements FoodItem {
protected BeverageSize size;
public Drink(BeverageSize size) {
super();
this.size = size;
if (this.size == null) {
this.size = BeverageSize.M;
}
}
public BeverageSize getSize() {
return size;
}
public String drinkDetails() {
return " (" + size + ")";
}
}
Code for ColdDrink
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.Drink;
import org.trishinfotech.builder.packing.Bottle;
import org.trishinfotech.builder.packing.Packing;
public abstract class ColdDrink extends Drink {
public ColdDrink(BeverageSize size) {
super(size);
}
public Packing packing() {
return new Bottle();
}
}
Code for CocaCola
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
public class CocaCola extends ColdDrink {
public CocaCola(BeverageSize size) {
super(size);
}
public String name() {
return "Coca-Cola" + drinkDetails();
}
public int calories() {
if (size != null) {
switch (size) {
case XS:
return 110;
case S:
return 150;
case M:
return 210;
case L:
return 290;
default:
break;
}
}
return 0;
}
public double price() {
if (size != null) {
switch (size) {
case XS:
return 0.80d;
case S:
return 1.0d;
case M:
return 1.5d;
case L:
return 2.0d;
default:
break;
}
}
return 0.0d;
}
}
Code for Pepsi
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
public class Pepsi extends ColdDrink {
public Pepsi(BeverageSize size) {
super(size);
}
public String name() {
return "Pepsi" + drinkDetails();
}
public int calories() {
if (size != null) {
switch (size) {
case S:
return 160;
case M:
return 220;
case L:
return 300;
default:
break;
}
}
return 0;
}
public double price() {
if (size != null) {
switch (size) {
case S:
return 1.2d;
case M:
return 2.2d;
case L:
return 2.7d;
default:
break;
}
}
return 0.0d;
}
}
Code for HotDrink
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.Drink;
import org.trishinfotech.builder.packing.Packing;
import org.trishinfotech.builder.packing.SipperMug;
public abstract class HotDrink extends Drink {
public HotDrink(BeverageSize size) {
super(size);
}
public Packing packing() {
return new SipperMug();
}
}
Code for Cuppuccinno
class
xxxxxxxxxx
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
public class Cappuccino extends HotDrink {
public Cappuccino(BeverageSize size) {
super(size);
}
public String name() {
return "Cappuccino" + drinkDetails();
}
public int calories() {
if (size != null) {
switch (size) {
case S:
return 120;
case M:
return 160;
case L:
return 210;
default:
break;
}
}
return 0;
}
public double price() {
if (size != null) {
switch (size) {
case S:
return 1.0d;
case M:
return 1.4d;
case L:
return 1.8d;
default:
break;
}
}
return 0.0d;
}
}
Code for HotChocolate
class:
xxxxxxxxxx
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
public class HotChocolate extends HotDrink {
public HotChocolate(BeverageSize size) {
super(size);
}
public String name() {
return "Hot Chocolate" + drinkDetails();
}
public int calories() {
if (size != null) {
switch (size) {
case S:
return 370;
case M:
return 450;
case L:
return 560;
default:
break;
}
}
return 0;
}
public double price() {
if (size != null) {
switch (size) {
case S:
return 1.6d;
case M:
return 2.3d;
case L:
return 3.0d;
default:
break;
}
}
return 0.0d;
}
}
Packing:
Code for Packing
interface:
xxxxxxxxxx
package org.trishinfotech.builder.packing;
public interface Packing {
public String pack();
public double packingPrice();
}
Code for Bottle
class:
xxxxxxxxxx
package org.trishinfotech.builder.packing;
public class Bottle implements Packing {
public String pack() {
return "Bottle";
}
public double packingPrice() {
return 0.75d;
}
}
Code for Container
class:
xxxxxxxxxx
package org.trishinfotech.builder.packing;
public class Container implements Packing {
public String pack() {
return "Container";
}
public double packingPrice() {
return 1.25d;
}
}
Code for MultiPack
class. MutiPack
packing to support Meal packing where we use different packing for different food items.
xxxxxxxxxx
package org.trishinfotech.builder.packing;
public class MultiPack implements Packing {
private double packingPrice;
public MultiPack(double packingPrice) {
super();
this.packingPrice = packingPrice;
}
public String pack() {
return "Multi-Pack";
}
public double packingPrice() {
return packingPrice;
}
}
Code for SipperMug
class:
x
package org.trishinfotech.builder.packing;
public class SipperMug implements Packing {
public String pack() {
return "Sipper Mug";
}
public double packingPrice() {
return 1.6d;
}
}
Code for Wrap
class:
xxxxxxxxxx
package org.trishinfotech.builder.packing;
public class Wrap implements Packing {
public String pack() {
return "Wrap";
}
public double packingPrice() {
return 0.40d;
}
}
Code for utility BillPrinter
class which I wrote to print an itemized bill.
x
package org.trishinfotech.builder.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.DoubleAdder;
import org.trishinfotech.builder.Order;
import org.trishinfotech.builder.OrderService;
import org.trishinfotech.builder.meal.Meal;
import org.trishinfotech.builder.packing.Packing;
public class BillPrinter {
static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
public static void printItemisedBill(Order order) {
OrderService service = order.getService();
System.out.printf("%60s\n", "Food Court");
System.out.println("=================================================================================================================");
System.out.printf("Service: %10s (%2.2f Tax) Customer Name: %-20s\n", service.getName(), service.getTax(), order.getCustomerName());
System.out.println("-----------------------------------------------------------------------------------------------------------------");
System.out.printf("%25s | %10s | %10s | %10s | %15s | %10s | %10s\n", "Food Item", "Calories", "Packing", "Price", "Packing Price", "Discount %", "Total Price");
System.out.println("-----------------------------------------------------------------------------------------------------------------");
DoubleAdder itemTotalPrice = new DoubleAdder();
order.getFoodItems().stream().forEach(item -> {
String name = item.name();
int calories = item.calories();
Packing packing = item.packing();
double price = item.price();
double packingPrice = packing.packingPrice();
double discount = item instanceof Meal? ((Meal)item).discount() : 0.0d;
double totalItemPrice = calculateTotalItemPrice(price, packingPrice, discount);
System.out.printf("%25s | %10d | %10s | %10.2f | %15.2f | %10.2f | %10.2f\n", name, calories, packing.pack(), price, packing.packingPrice(), discount, totalItemPrice);
itemTotalPrice.add(totalItemPrice);
});
System.out.println("=================================================================================================================");
double billTotal = itemTotalPrice.doubleValue();
billTotal = applyTaxes(billTotal, service);
System.out.printf("Date: %-30s %66s %.2f\n", dtf.format(LocalDateTime.now()), "Total Bill (incl. taxes):", billTotal);
System.out.println("Enjoy your meal!\n\n\n\n");
}
private static double applyTaxes(double billTotal, OrderService service) {
return billTotal + (billTotal * service.getTax())/100;
}
private static double calculateTotalItemPrice(double price, double packingPrice, double discount) {
if (discount > 0.0d) {
price = price - (price * discount)/100;
}
return price + packingPrice;
}
}
Now, since almost everything is ready. It's time to write our immutable Order
class:
xxxxxxxxxx
package org.trishinfotech.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.trishinfotech.builder.meal.FoodItem;
public class Order {
private List<FoodItem> foodItems = new ArrayList<FoodItem>();
private String customerName;
private OrderService service;
public Order(OrderService service, List<FoodItem> foodItems, String customerName) {
super();
if (Objects.isNull(service)) {
throw new IllegalArgumentException(
"Meal can't be order without selecting service 'Take Away' or 'Eat Here'");
}
if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) {
throw new IllegalArgumentException(
"Meal can't be order without any food item");
}
this.service = service;
this.foodItems = new ArrayList<FoodItem>(foodItems);
this.customerName = customerName;
if (this.customerName == null) {
this.customerName = "NO NAME";
}
}
public List<FoodItem> getFoodItems() {
return foodItems;
}
public String getCustomerName() {
return customerName;
}
public OrderService getService() {
return service;
}
}
And now here is the code for OrderBuilder
, which will build the Order
object.
xxxxxxxxxx
package org.trishinfotech.builder;
import java.util.ArrayList;
import java.util.List;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.cold.CocaCola;
import org.trishinfotech.builder.beverages.cold.Pepsi;
import org.trishinfotech.builder.food.burger.ChickenBurger;
import org.trishinfotech.builder.food.burger.VegBurger;
import org.trishinfotech.builder.food.nuggets.CheeseNuggets;
import org.trishinfotech.builder.food.nuggets.ChickenNuggets;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.meal.Meal;
public class OrderBuilder {
protected static final double HAPPY_MENU_DISCOUNT = 5.0d;
private String customerName;
private OrderService service = OrderService.TAKE_AWAY;
private List<FoodItem> items = new ArrayList<FoodItem>();
public OrderBuilder() {
super();
}
// Setters for each of the fields we have in the target object. In this example its Order.
// We are having return type as Builder itself (i.e. OrderBuilder) to make chained calling of setters possible.
public OrderBuilder name(String customerName) {
this.customerName = customerName;
return this;
}
public OrderBuilder service(OrderService service) {
if (service != null) {
this.service = service;
}
return this;
}
public OrderBuilder item(FoodItem item) {
items.add(item);
return this;
}
// Happy Menus
public OrderBuilder vegNuggetsHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new CheeseNuggets());
foodItems.add(new Pepsi(BeverageSize.S));
Meal meal = new Meal("Veg Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder chickenNuggetsHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new ChickenNuggets());
foodItems.add(new CocaCola(BeverageSize.S));
Meal meal = new Meal("Chicken Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder vegBurgerHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new VegBurger());
foodItems.add(new Pepsi(BeverageSize.S));
Meal meal = new Meal("Veg Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder chickenBurgerHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new ChickenBurger());
foodItems.add(new CocaCola(BeverageSize.S));
Meal meal = new Meal("Chicken Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public Order build() {
Order order = new Order(service, items, customerName);
if (!validateOrder()) {
System.out.println("Sorry! Order can't be placed without service type (Take Away/Eat Here) and any food item.");
return null;
}
return order;
}
private boolean validateOrder() {
return (service != null) && !items.isEmpty();
}
}
Done! Now it's time to write a Main program to execute and test the output:
xxxxxxxxxx
package org.trishinfotech.builder;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.cold.CocaCola;
import org.trishinfotech.builder.beverages.cold.Pepsi;
import org.trishinfotech.builder.beverages.hot.HotChocolate;
import org.trishinfotech.builder.food.burger.ChickenBurger;
import org.trishinfotech.builder.food.nuggets.CheeseNuggets;
import org.trishinfotech.builder.food.nuggets.ChickenNuggets;
import org.trishinfotech.builder.util.BillPrinter;
public class Main {
public static void main(String[] args) {
OrderBuilder builder1 = new OrderBuilder();
// you can see the use of chained calls of setters here. No statement terminator
// till we set all the values of the object
Order meal1 = builder1.name("Brijesh").service(OrderService.TAKE_AWAY).item(new ChickenBurger())
.item(new Pepsi(BeverageSize.M)).vegNuggetsHappyMeal().build();
BillPrinter.printItemisedBill(meal1);
OrderBuilder builder2 = new OrderBuilder();
Order meal2 = builder2.name("Micheal").service(OrderService.EAT_HERE).item(new ChickenNuggets())
.item(new CheeseNuggets()).item(new CocaCola(BeverageSize.L)).chickenBurgerHappyMeal()
.item(new HotChocolate(BeverageSize.M)).vegBurgerHappyMeal().build();
BillPrinter.printItemisedBill(meal2);
}
}
And here's the output of the program:
x
Food Court
=================================================================================================================
Service: Take Away (2.00 Tax) Customer Name: Brijesh
-----------------------------------------------------------------------------------------------------------------
Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price
-----------------------------------------------------------------------------------------------------------------
Chicken Burger | 300 | Wrap | 4.50 | 0.40 | 0.00 | 4.90
Pepsi (M) | 220 | Bottle | 2.20 | 0.75 | 0.00 | 2.95
Veg Nuggets Happy Meal | 490 | Multi-Pack | 5.00 | 2.00 | 5.00 | 6.75
=================================================================================================================
Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 14.89
Enjoy your meal!
Food Court
=================================================================================================================
Service: Eat Here (5.50 Tax) Customer Name: Micheal
-----------------------------------------------------------------------------------------------------------------
Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price
-----------------------------------------------------------------------------------------------------------------
Chicken Nuggets | 450 | Container | 5.00 | 1.25 | 0.00 | 6.25
Cheese Nuggets | 330 | Container | 3.80 | 1.25 | 0.00 | 5.05
Coca-Cola (L) | 290 | Bottle | 2.00 | 0.75 | 0.00 | 2.75
Chicken Burger Happy Meal | 450 | Multi-Pack | 5.50 | 1.15 | 5.00 | 6.38
Hot Chocolate (M) | 450 | Sipper Mug | 2.30 | 1.60 | 0.00 | 3.90
Veg Burger Happy Meal | 340 | Multi-Pack | 3.90 | 1.15 | 5.00 | 4.86
=================================================================================================================
Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 30.78
Enjoy your meal!
Well, there you have it! I hope this tutorial helped to understand Builder pattern.
Source Code can be found here: Real-Builder-Design-Pattern-Source-Code
and Builder-Design-Pattern-Sample-Code
Liked the article? Please don't forget to press that like button. Happy coding!
Need more articles on Design Patterns? Please visit my profile to find more: Brijesh Saxena
Opinions expressed by DZone contributors are their own.
Comments