The builder Pattern, make it semantic

The purpose of the builder pattern is to simplify the construction of complex objects, providing us a method to create different representations by using the same construction process.

It’s not uncommon to create objects that need to receive lots of arguments to be properly initialized and in a usable state.

There are at least two ways to achieve this. We can Use a parametrized constructor or we can use setters.

Let’s see the problems that can arise from this “standard” way to do things.

The constructor problem

let’s imagine you are examining some code and come across a line like,

Window window = new Window (100,100,200,300,"settings",35,127,32)

What information do you get from there ? What are all those values for ?

We don’t know, so, although the IDE may help a little here, we normally end up visiting the Window class, just to make sure. It may be simple, and surely works, but the true is that an extra layer of complexity was inserted in this code, as it is not easily understandable.

The setter’s problem

Although using setters will overcome the problem of us not knowing what the values refer to, there is a severe hidden problem.

To access the setters, we first need to instantiate the object, and that means that the object already exists somewhere in the heap, but until we have all the setters “properly setted”, the object is in an inconsistent state. Using it, before it is fully formed, may lead to unpredictable results. In some applications, this is a disaster waiting to happen.

There must be a better way, and that’s where the builder pattern comes in.

The idea is to build our object, step by step, and in the end we will receive the object fully formed.

The steps we will use will be,

  1. Create a class that represents our object with all the needed attributes.
  2. Create a static inner class an in this inner class we copy all the attributes of the outer class. Convention dictates that the inner class should be named as the outer class with the suffix builder.
  3. The builder class should have a public constructor with all attributes as parameters.
  4. In the inner class we should make available a public method named builder (again, this name is a convention) that will return our object. To properly achieve this we will use a private constructor in the outer class that will receive as argument it’s inner class.

NOTE : we may have more than one builder class, each one with different steps to create the object.

Here we are using the fact that in Java, a static inner class acts as a static member of the outer class, and it can be accessed without instantiating the outter class.

After implementing a builder, the line we showed above as,

Window window = new Window (100,100,200,300,"settings",35,127,32)

will become

`Window window = new Window.WindowBuilder()
                    .withLeftTopcornerPositionX(100)
                    .withLeftTopCornerPositionY(100)
                    .withHeight(300)
                    .withWidth(600)
                    .fillWithRGB(125,180,28)
                    .withTitle("Settings")
                    .build();`

Now, besides coding, we are actually communicating in an elegant way. Anyone that comes across this code, will easily understand what it does.

So, let’s see the implementation,

Window.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package BuilderPattern;

public class Window {

private final int topCornerX;
private final int topCornerY;
private final int windowWidth;
private final int windowHeight;
private final int fillRedValue;
private final int fillGreenValue;
private final int fillBlueValue;
private final String windowTitle;

// begin of inner class
public static final class WindowBuilder {
private int topCornerX; //if nothing is said, default value will be zero
private int topCornerY; //if nothing is said, default value will be zero
// if no values are send to builder, this will be the default values
private int windowWidth = 300;
private int windowHeight = 300;
private int fillRedValue = 255;
private int fillGreenValue = 255;
private int fillBlueValue = 255;
private String windowTitle = "untitled";


public WindowBuilder withLeftTopcornerPositionX(int topCornerX) {

//we don't want our X coordinates out of this range
if (isWithinRange(topCornerX, 0, 1024)) {
topCornerX = 0;
}

this.topCornerX = topCornerX;
return this;
}

public WindowBuilder withLeftTopCornerPositionY(int topCornerY) {

//we don't want our Y coordinates out of this range
if (isWithinRange(topCornerY, 0, 800)) {
topCornerY = 0;
}

this.topCornerY = topCornerY;
return this;
}

public WindowBuilder withWidth(int windowWidth) {

//we don't want our width out of this range
if (isWithinRange(windowWidth, 1, 600)) {
windowWidth = 300;
}

this.windowWidth = windowWidth;
return this;
}

public WindowBuilder withHeight(int windowHeight) {

//we don't want our height out of this range
if (isWithinRange(windowHeight, 1, 600)) {
windowHeight = 300;
}

this.windowHeight = windowHeight;
return this;
}

public WindowBuilder fillWithRGB(int red, int green, int blue) {

//we don't want any of the RGB values out of the range (0-255)
if ((isWithinRange(red, 0, 255) | (isWithinRange(green, 0, 255)) | (isWithinRange(blue, 0, 255)))) {

red = 255;
green = 255;
blue = 255;
}

this.fillRedValue = red;
this.fillGreenValue = green;
this.fillBlueValue = blue;
return this;

}

public WindowBuilder withTitle(String title) {

if (title != null && !title.equals("")) {
this.windowTitle = title;
} else {
this.windowTitle = " untitled ";
}
return this;
}

public Window build() {
return new Window(this);
}

/*
* verifies if a value is out of accepted range
*/
private boolean isWithinRange(int value, int min, int max) {

return (value < min || value > max);
}

}// end of inner class

public Window(WindowBuilder builder) {
this.topCornerX = builder.topCornerX;
this.topCornerY = builder.topCornerY;
this.windowHeight = builder.windowHeight;
this.windowWidth = builder.windowWidth;
this.fillRedValue = builder.fillRedValue;
this.fillGreenValue = builder.fillGreenValue;
this.fillBlueValue = builder.fillBlueValue;
this.windowTitle = builder.windowTitle;
}

@Override
public String toString() {
return "I'm creating a window with a name " + windowTitle + " top left X corner = " + topCornerX + " top left Y corner = " + topCornerY + " with a width of " + windowWidth + " and height of " + windowHeight +
" I will fill the window with the color (" + fillRedValue + "," + fillGreenValue + "," + fillBlueValue + ")";
}

public int getTopCornerX() {
return topCornerX;
}

public int getTopCornerY() {
return topCornerY;
}

public int getWindowWidth() {
return windowWidth;
}

public int getWindowHeight() {
return windowHeight;
}

public int getFillRedValue() {
return fillRedValue;
}

public int getFillGreenValue() {
return fillGreenValue;
}

public int getFillBlueValue() {
return fillBlueValue;
}

public String getWindowTitle() {
return windowTitle;
}
}

Now, that may seems a lot of work, but trust me, it will pay off in the long run.

An example for the code in this article can be found in git-hub

Share