Pattern from the practical life of a software developer

Builder-Pattern
The book from the “gang of four” is part of the essential reading in just about every computer science branch. The basic patterns are described and grouped to get a good start on the topic of design patterns. But how does it look later in use?
Here we will take a closer look at one pattern and expand it.
The Pattern – Builder
The builder pattern is currently enjoying increasing popularity as it allows you to build a fluent API.
It is also lovely that an IDE can generate this pattern quite quickly. But how about using this design pattern in daily life?
The basic builder pattern
Let’s start with the basic pattern, the initial version with which we have already gained all our experience.
For example, I’ll take a Car class with the Engine and List <Wheels> attributes. A car’s description is certainly not very precise, but it is enough to demonstrate some specific builder-pattern behaviours.
Now let’s start with the Car class.
public class Car {
private Engine engine;
private List wheelList;
//SNIPP
}
At this point, I leave out the get and set methods in this listing. If you generate a builder for this, you get something like the following.
public static final class Builder {
private Engine engine;
private List<Wheel> wheelList;
private Builder() {
}
public Builder withEngine(Engine engine) {
this.engine = engine;
return this;
}
public Builder withWheelList(List<Wheel> wheelList) {
this.wheelList = wheelList;
return this;
}
public Car build() {
return new Car(this);
}
}
Here the builder is implemented as a static inner class. The constructor of the “Car” class has also been modified.
private Car(Builder builder) {
setEngine(builder.engine);
wheelList = builder.wheelList;
}
On the one hand, there has been a change from public to private, and on the other hand, an instance of the builder has been added as a method parameter.
Car car = Car.newBuilder()
.withEngine(engine)
.withWheelList(wheels)
An example – the car
If you now work with the Builder Pattern, you get to the point where you have to build complex objects. Let us now extend our example by looking at the remaining attributes of the Car class.
public class Car {
private Engine engine;
private List<Wheel> wheelList;
}
public class Engine {
private int power;
private int type;
}
public class Wheel {
private int size;
private int type;
private int colour;
}
Now you can have a corresponding builder generated for each of these classes. If you stick to the basic pattern, it looks something like this for the class Wheel:
public static final class Builder {
private int size;
private int type;
private int colour;
private Builder() {}
public Builder withSize(int size) {
this.size = size;
return this;
}
public Builder withType(int type) {
this.type = type;
return this;
}
public Builder withColour(int colour) {
this.colour = colour;
return this;
}
public Wheel build() {
return new Wheel(this);
}
}
But what does it look like if you want to create an instance of the class Car? For each complex attribute of Car, we will create an instance using the builder. The resulting source code is quite extensive; at first glance, there was no reduction in volume or complexity.
public class Main {
public static void main(String[] args) {
Engine engine = Engine.newBuilder().withPower(100).withType(5).build();
Wheel wheel1 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
Wheel wheel2 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
Wheel wheel3 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
List<Wheel> wheels = new ArrayList<>();
wheels.add(wheel1);
wheels.add(wheel2);
wheels.add(wheel3);
Car car = Car.newBuilder()
.withEngine(engine)
.withWheelList(wheels)
.build();
System.out.println("car = " + car);
}
}
This source code is not very nice and by no means compact. So how can you adapt the builder pattern here so that on the one hand you have to write as little as possible by the builder himself and on the other hand you get more comfort when using it?
WheelListBuilder
Let’s take a little detour first. To be able to raise all potentials, we have to make the source text homogeneous. This strategy enables us to recognize patterns more easily. In our example, the creation of the List<Wheel> is to be outsourced to a builder, a WheelListBuilder.
public class WheelListBuilder {
public static WheelListBuilder newBuilder(){
return new WheelListBuilder();
}
private WheelListBuilder() {}
private List<Wheel> wheelList;
public WheelListBuilder withNewList(){
this.wheelList = new ArrayList<>();
return this;
}
public WheelListBuilder withList(List wheelList){
this.wheelList = wheelList;
return this;
}
public WheelListBuilder addWheel(Wheel wheel){
this.wheelList.add(wheel);
return this;
}
public List<Wheel> build(){
//test if there are 4 instances....
return this.wheelList;
}
}
Now our example from before looks like this:
public class Main {
public static void main(String[] args) {
Engine engine = Engine.newBuilder().withPower(100).withType(5).build();
Wheel wheel1 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
Wheel wheel2 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
Wheel wheel3 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();
List<Wheel> wheelList = WheelListBuilder.newBuilder()
.withNewList()
.addWheel(wheel1)
.addWheel(wheel2)
.addWheel(wheel3)
.build();//more robust if you add tests at build()
Car car = Car.newBuilder()
.withEngine(engine)
.withWheelList(wheelList)
.build();
System.out.println("car = " + car);
}
}
Next, we connect the Wheel class builder and the WheelListBuilder class. The goal is to get a fluent API so that we don’t create the instances of the Wheel class individually and then use the addWheel(Wheel w) method to WheelListBuilder need to add. It should then look like this for the developer in use:
List wheels = wheelListBuilder
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.build();
So what happens here is the following: As soon as the addWheel() method is called, a new instance of the class WheelBuilder should be returned. The addWheelToList() method creates the representative of the Wheel class and adds it to the list. To do that, you have to modify the two builders involved. The addWheelToList() method is added to the WheelBuilder side. This adds the instance of the Wheel class to the WheelListBuilder and returns the instance of the WheelListBuilder class.
private WheelListBuilder wheelListBuilder;
public WheelListBuilder addWheelToList(){
this.wheelListBuilder.addWheel(this.build());
return this.wheelListBuilder;
}
On the side of the WheelListBuilder class, only the method addWheel() is added.
public Wheel.Builder addWheel() {
Wheel.Builder builder = Wheel.newBuilder();
builder.withWheelListBuilder(this);
return builder;
}
If we now transfer this to the other builders, we come to a pretty good result:
Car car = Car.newBuilder()
.addEngine().withPower(100).withType(5).done()
.addWheels()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.addWheel().withType(1).withSize(2).withColour(2).addWheelToList()
.done()
.build();
The NestedBuilder
So far, the builders have been modified individually by hand. However, this can be implemented generically quite easily since it is just a tree of builders.
Every builder knows his children and his father. The implementations required for this can be found in the NestedBuilder class. It is assumed here that the methods for setting attributes always begin with the prefix with. Since this seems to be the case with most generators for builders, no manual adjustment is necessary here. The method done() sets the result of his method build() on his father. The call is made using reflection. With this, a father knows the authority of the child. At this point, I assume that the name of the attribute is the same as the class name. We will see later how this can be achieved with different attribute names. The method withParentBuilder(..) enables the father to announce himself to his child. We have a bidirectional connection now.
public abstract class NestedBuilder<T, V> {
public T done() {
Class<?> parentClass = parent.getClass();
try {
V build = this.build();
String methodname = "with" + build.getClass().getSimpleName();
Method method = parentClass.getDeclaredMethod(methodname, build.getClass());
method.invoke(parent, build);
} catch (NoSuchMethodException
| IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
}
return parent;
}
public abstract V build();
protected T parent;
public <P extends NestedBuilder<T, V>> P withParentBuilder(T parent) {
this.parent = parent;
return (P) this;
}
}
Now the specific methods for connecting with the children can be added to a father. There is no need to derive from NestedBuilder.
public class Parent {
private KidA kidA;
private KidB kidB;
//snipp.....
public static final class Builder {
private KidA kidA;
private KidB kidB;
//snipp.....
// to add manually
private KidA.Builder builderKidA = KidA.newBuilder().withParentBuilder(this);
private KidB.Builder builderKidB = KidB.newBuilder().withParentBuilder(this);
public KidA.Builder addKidA() { return this.builderKidA; }
public KidB.Builder addKidB() { return this.builderKidB; }
//---------
public Parent build() {
return new Parent(this);
}
}
}
And with the children, it looks like this: Here, you only have to derive from NestedBuilder.
public class KidA {
private String note;
//snipp.....
public static final class Builder extends NestedBuilder<Parent.Builder, KidA> {
//snipp.....
}
}
The use is then very compact, as shown in the previous example.
public class Main {
public static void main(String[] args) {
Parent build = Parent.newBuilder()
.addKidA().withNote("A").done()
.addKidB().withNote("B").done()
.build();
System.out.println("build = " + build);
}
}
Any combination is, of course, also possible. This means that a proxy can be a father and child at the same time. Nothing stands in the way of building complex structures.
public class Main {
public static void main(String[] args) {
Parent build = Parent.newBuilder()
.addKidA().withNote("A")
.addKidB().withNote("B").done()
.done()
.build();
System.out.println("build = " + build);
}
}
Happy Coding