Builder Pattern

BUILDER

It is one of the design patterns included in the group of construction patterns. The process of creating an object is separate from its real representation here. 

However, there are two popular variations of this pattern which can lead to confusion in its understanding. The first variety was described by the authors of the book Design Patterns (Erich Gamma, Richard Helm, Ralph Johnson, John Vissides – also known as GoF – Gang of Four). The second variant (being a modification of the first one) was introduced by Joshua Bloch. Therefore, in this article, I would like to briefly discuss both of these methods.

GoF variation

The builder is most often used in situations where:

  • The process of creating an object is a multi-step process and you cannot instantiate this object by a one-time operation.
  • We cannot simplify the complexity of the object we create.
  • An object will be built many times and in many ways.

The basis of the pattern is the existence of the Builder interface which we will use to create real objects. Let’s try, using an example of the Car class below, to write the assembly process of its copy.

public class Car {

private String wheels;
private String engine;
private String body;

public void setWheels(String wheels){
this.wheels = wheels;
}
public void setEngine(String engine){
this.engine = engine;
}
public void setBody(String body){
this.body = body;
}

public String getWheels() {return wheels;}
public String getEngine() {return engine;}
public String getBody() {return body;}
}

So let’s create the aforementioned Builder as an interface that will contain the necessary methods to assemble the car:

public interface Builder {

void wheelsAssembly();
void engineAssembly();
void bodyAssembly();

Car carAssembly();
}

Next, we’ll create the actual Small Car Builder class using the methods included in Builder:

public class SmallCarsBuilder implements Builder{

private Car car = new Car();

@Override
public void wheelsAssembly() { car.setWheels("Small wheels"); }

@Override
public void engineAssembly() { car.setEngine("Small engine"); }

@Override
public void bodyAssembly() {
car.setBody("Body of a small car");
}


public Car carAssembly(){

System.out.println(
"\n" + car.getWheels() +
"\n" + car.getEngine() +
"\n" + car.getBody()
);

System.out.println("\nA new small car is ready");
return car;
}
}

The only thing left for us to do is hire a real manager who will be responsible for delivering a given car model. The manager has a trusted fitter at his disposal who knows perfectly well how to assemble a given car model:

public class AssemblyLineManager {

public void BuildACar(Builder fitter){

fitter.wheelsAssembly();
fitter.engineAssembly();
fitter.bodyAssembly();
}
}

Now our SmallCarsBuilder is ready to create new Car class objects. Let’s test our solution by building a new car.

public class NewCarAssemblyTest {

public static void main (String[] args){

AssemblyLineManager assemblyLineManager = new AssemblyLineManager();
SmallCarsBuilder smallCar = new SmallCarsBuilder();

assemblyLineManager.BuildACar(smallCar);
smallCar.carAssembly(); // just to show how the assembly process looks
}
}

Here is the result:

Small wheels
Small engine
Body of a small car
A new small car is ready

GoF Summary

In the example above, we can see how the structure of this pattern is built. Here we have the aforementioned Builder interface with which we create a real concrete SmallCar Builder class. We see that the Assembly Manager, using a real builder (fitter), is able to provide us with a specific car model. This pattern is most often used to build complex composite structures.

Static Builder variation

Let’s use the already known Car class in our example and then modify it a bit by adding a static Fitter class inside this class, which will be our Static Builder.

Additionally, let’s set the constructor of the Fitter class with one mandatory parameter (wheels) and two optional parameters (engine and structure).

public class Car {

private final int wheels;
private final String engine;
private final String body;

private Car(Car.Fitter fitter) {
this.wheels = fitter.wheels;
this.engine = fitter.engine;
this.body = fitter.body;
}

public String toString() {
return "A new car has been assembled: \n Wheels size: " + this.wheels + " inches, " + this.engine + ", " + this.body;
}

public static class Fitter{
private final int wheels;
private String engine;
private String body;


public Fitter(int wheels){
this.wheels = wheels;
}

public Car.Fitter engine(String engine){
this.engine = engine;
return this;
}

public Car.Fitter body(String body){
this.body = body;
return this;
}

public Car build() {

if (!List.of(15,17,19).contains(this.wheels)) {
throw new IllegalArgumentException("The wheel size must be: 15, 17 or 19 inches");
}

return new Car(this);
}
}
}

In the build () car class method, we check whether the size of the wheels set for a given model is consistent with our specification.

We will use the Car class prepared in this way to create new objects of this class:

public class NewCarAssemblyTest {

public static void main(String[] args){
Car smallCar = new Car.Fitter(17)
.engine("Small engine 1.0")
.body("Small car body")
.build();
System.out.println(smallCar);

Car bigCar = new Car.Fitter(19)
.engine("Six cylinder in-line engine")
.body("Jeep size body")
.build();

System.out.println(bigCar);
}
}

Here is the result:

A new car has been assembled: 
Wheels size: 17 inches, Small engine 1.0, Small car body
A new car has been assembled:
Wheels size: 19 inches, Six cylinder in-line engine, Jeep size body

We see how we have dynamically created two different car models. At the same time, we checked whether the given wheel size complies with the specification adopted by us.

Of course, it took place at the expense of a significant expansion of the Car class, however, we no longer have other classes that are part of the final product. A static builder is often used to build objects with multi-parameter constructors.

Summary

I hope that the presentation of the above examples will help you to look at the builder pattern from several perspectives because reading other articles myself I did not understand why in some cases the static method is used, while in others the code is based on the foundations of an abstract class and a manager.

Leave a Comment

Your email address will not be published. Required fields are marked *