Definition of the Builder Design Pattern
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
Introduction
Let us take an example of constructing a House object. A house will have a number of bedrooms, and bathrooms. For this discussion let us assume that the house can have at most one kitchen and a garage.
From the definition of the builder design pattern, the complex object we are constructing is the House object. The construction process involves building the various rooms (bedroom, bathroom, etc) i.e., the component parts that make up a house. Next, we can also have different representations of the house. For example, in a standard representation, all the rooms will be normal looking. When building a palace, each of the components/rooms will be different, though they have the same dimension.
The builder pattern helps us to separate the object’s representation (normal building vs palace) and the construction process (what rooms and how many rooms we add to the building).
Building the house using the Builder Design Pattern (representation)
First, let us create the POJOs for the component of the house. Each room has a length and a width. In addition to this, some of the rooms have a floor type. To keep the example simple, I’m not adding more details to the rooms.
public class Room {
private int length;
private int width;
public Room(int length, int width) {
this.length = length;
this.width = width;
}
}
public enum FloorCeramicType {
MARBLE, MOSAIC, GRANITE;
}
public class Bedroom extends Room {
private FloorCeramicType floorCeramicType;
public Bedroom(int length, int width) {
super(length, width);
}
public Bedroom(int length, int width, FloorCeramicType floorCeramicType) {
super(length, width);
this.floorCeramicType = floorCeramicType;
}
}
public class Bathroom extends Room {
public Bathroom(int length, int width) {
super(length, width);
}
}
public class Kitchen extends Room {
private FloorCeramicType floorCeramicType;
public Kitchen(int length, int width) {
super(length, width);
}
public Kitchen(int length, int width, FloorCeramicType floorCeramicType) {
super(length, width);
this.floorCeramicType = floorCeramicType;
}
}
public class Garage {
private int length;
private int width;
public Garage(int length, int width) {
this.length = length;
this.width = width;
}
}
HouseBuilder is the interface for building various parts of a building or house. Each of the rooms have to built by calling various methods on this builder interface. The details of the representation of the house is not in this interface. The implementation of this builder will take care of how to build the components of a house.
public interface HouseBuilder {
void addBedroom(int length, int width);
void addBedroom(int length, int width, FloorCeramicType floorCeramicType);
void addBathroom(int length, int width);
void garage(int length, int width);
void kitchen(int length, int width);
void kitchen(int length, int width, FloorCeramicType floorCeramicType);
}
public class StandardHouseBuilder implements HouseBuilder {
private House house = new House();
@Override
public void addBedroom(int length, int width) {
Bedroom bedroom = new Bedroom(length, width);
house.addBedRoom(bedroom);
}
@Override
public void addBedroom(int length, int width, FloorCeramicType floorCeramicType) {
Bedroom bedroom = new Bedroom(length, width, floorCeramicType);
house.addBedRoom(bedroom);
}
@Override
public void addBathroom(int length, int width) {
Bathroom bathroom = new Bathroom(length, width);
house.addBathroom(bathroom);
}
@Override
public void garage(int length, int width) {
Garage garage = new Garage(length, width);
house.setGarage(garage);
}
@Override
public void kitchen(int length, int width) {
Kitchen kitchen = new Kitchen(length, width);
house.setKitchen(kitchen);
}
@Override
public void kitchen(int length, int width, FloorCeramicType floorCeramicType) {
Kitchen kitchen = new Kitchen(length, width, floorCeramicType);
house.setKitchen(kitchen);
}
public House getHouse() {
return house;
}
}
The StandardHouseBuilder is a simple implementation of the HouseBuilder interface. When we create a new StandardHouseBuilder, it creates an empty building. Each of the builder methods builds the appropriate room and adds to the house. Finally, there is build method that returns the built room object.
House construction
So far, we have seen the builder interface and implementation to build the parts of a house. Next, we will move to the construction of the house. In the builder design pattern this is called the director. It is responsible for building the real house by calling the methods on the builder interface.
Let us write a director to construct a house with one bedroom, one bathroom, a kitchen, and a garage.
public class OneBedroomHouseBuildDirector {
private final HouseBuilder houseBuilder;
public OneBedroomHouseBuildDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public void constructHouse() {
houseBuilder.addBedroom(100, 100);
houseBuilder.addBathroom(40, 50);
houseBuilder.kitchen(120, 80);
houseBuilder.garage(60, 60);
}
}
Building the house
We now have a builder and a director. Let us see how to use these to build a house. The pattern is as follows.
- Create an implementation of a builder interface (the Builder object)
- The client will create a new Director object and passes it the builder object.
- The client calls a method on the director to begin the construction process. The director calls the various builder methods to build the object.
- The builder receiving the request from the director will begin building the parts of the product (rooms of the house here)
- The client then gets the final built product from the builder implementation.
Let us see this in action.
StandardHouseBuilder standardHouseBuilder = new StandardHouseBuilder();
OneBedroomHouseBuildDirector oneBedroomHouseBuildDirector =
new OneBedroomHouseBuildDirector(standardHouseBuilder);
oneBedroomHouseBuildDirector.constructHouse();
House house = standardHouseBuilder.getHouse();
//Use the house
We create the Builder StandardHouseBuilder and pass it to the OneBedroomHouseBuildDirector. We then call the constructHouse method on the director. It will begin to build the bedrooms, kitchens etc. After the call returns, we can get the final house by calling the getHouse method on the builder object.
Varying the director (construction)
Let us vary the construction process by swapping the above director with a new director that builds a house with two bedrooms.
public class TwoBedroomHouseBuildDirector {
private final HouseBuilder houseBuilder;
public TwoBedroomHouseBuildDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public void constructHouse() {
houseBuilder.addBedroom(100, 100);
houseBuilder.addBedroom(120, 120);
houseBuilder.addBathroom(40, 50);
houseBuilder.kitchen(120, 80);
houseBuilder.garage(60, 60);
}
}
StandardHouseBuilder standardHouseBuilder = new StandardHouseBuilder();
TwoBedroomHouseBuildDirector twoBedroomHouseBuildDirector =
new TwoBedroomHouseBuildDirector(standardHouseBuilder); //new director
twoBedroomHouseBuildDirector.constructHouse();
House house = standardHouseBuilder.getHouse();
//Use the house
The above built house will have two bedrooms.
Varying the builder (representation)
As I said earlier, we can write a new Builder that uses room objects belonging to a palace, i.e., the individual room objects used will be different (costly and fancy) for a palace.
Let me show a builder that does not actually build a room. Instead, it counts the number of bedrooms, bathrooms and keeps track of if a kitchen and a garage is present. This kind of implementation can be used as a fake object in unit tests.
public class CountingHouseBuilder implements HouseBuilder {
private int numBedrooms;
private int numBathrooms;
private boolean kitchenPresent;
private boolean garagePresent;
@Override
public void addBedroom(int length, int width) {
numBedrooms++;
}
@Override
public void addBedroom(int length, int width, FloorCeramicType floorCeramicType) {
numBedrooms++;
}
@Override
public void addBathroom(int length, int width) {
numBathrooms++;
}
@Override
public void garage(int length, int width) {
garagePresent = true;
}
@Override
public void kitchen(int length, int width) {
kitchenPresent = true;
}
@Override
public void kitchen(int length, int width, FloorCeramicType floorCeramicType) {
kitchenPresent = true;
}
public int getNumBedrooms() {
return numBedrooms;
}
public int getNumBathrooms() {
return numBathrooms;
}
public boolean isKitchenPresent() {
return kitchenPresent;
}
public boolean isGaragePresent() {
return garagePresent;
}
}
CountingHouseBuilder countingHouseBuilder = new CountingHouseBuilder();
oneBedroomHouseBuildDirector =
new OneBedroomHouseBuildDirector(countingHouseBuilder);
oneBedroomHouseBuildDirector.constructHouse();
System.out.println("Number of bedrooms = " + countingHouseBuilder.getNumBedrooms());
System.out.println("Number of bathrooms = " + countingHouseBuilder.getNumBathrooms());
System.out.println("Is Kitchen present: " + countingHouseBuilder.isKitchenPresent());
System.out.println("Is Garage present: " + countingHouseBuilder.isGaragePresent());
Prints,
Number of bedrooms = 1
Number of bathrooms = 1
Is Kitchen present: true
Is Garage present: true
You can also try using the TwoBedroomHouseBuildDirector director when using the CountingHouseBuilder. It will print as having two bedrooms.
Structure
Participants
Builder(HouseBuilder)
- It specifies an interface for creating various parts of a Product.
ConcreteBuilder(StandardHouseBuilder, CountingHouseBuilder)
- Implments the builder interface
- Builds the parts of the Product.
- Provides a method for getting the built Product.
Director(OneBedroomHouseBuildDirector, TwoBedroomHouseBuildDirector)
- Constructs an object by using the Builder.
Product(House)
- It is the object that we are constructing.
- A ConcreteBuilder is responsible for building the individual parts of this product.
Summary
The Builder design pattern separates the construction of an object from its representation. The Builder implementation takes care of assembling the internals of the Product and the how-part of object construction is encapsulated within the builder. Here, the Director calls the builder methods to build the object. The director is composed with a builder.
By changing the director (construction process), we can build objects with different configurations - like a standard house with one or two bedrooms.
When we change the concrete builder, we can build different types (representation) of object (with the same construction process) - like one bedroom house or a one bedroom palace.
Related design pattern
The Builder design pattern is very closely related to the Abstract Factory Design Pattern. In the abstract factory pattern, we create an interface for creating families of related objects. In that one implementation of the abstract factory will create objects of family 1 and another implementation will create objects of family 2. Refer to the structure of the Abstract Factory Design Pattern.
It is similar to the Builder pattern. The difference is that a Builder pattern constructs a complex object step by step, i.e., each method on the builder builds a part or a component of a product. The final product is returned after the director is done with the construction (we call a method on the Builder to get the built object). But in an abstract factory, each method returns a product directly.
Conclusion
In this post we learnt about the Builder Design Pattern. It is used for building a complex object by separating the algorithm or steps to create an object from the logic that takes care of assembling the object. This enables us to switch the construction logic or use different representations of the product independently.
I would be happy to hear your thoughts or questions on this.
References
- Head First Design Patterns: A Brain-Friendly Guide by Eric Freeman and Elisabeth Robson.
- Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
- https://en.wikipedia.org/wiki/Builder_pattern
- https://howtodoinjava.com/design-patterns/creational/builder-pattern-in-java/