This is a creational pattern, as it is used to control class instantiation. 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.
Elements involved:
- Product: The product class defines the type of the complex object that is to be generated by the builder pattern.
- Builder: This abstract base class defines all of the steps that must be taken in order to correctly create a product. Each step is generally abstract as the actual functionality of the builder is carried out in the concrete subclasses. The getProduct method is used to return the final product. The builder class is often replaced with a simple interface.
- ConcreteBuilder: There may be any number of concrete builder classes inheriting from Builder. These classes contain the functionality to create a particular complex product.
- Director: The director class controls the algorithm that generates the final product object. A director object is instantiated and its construct method is called. The method includes a parameter to capture the specific concrete builder object that is to be used to generate the product. The director then calls methods of the concrete builder in the correct order to generate the product object. On completion of the process, the getProduct method of the builder object can be used to return the product.
We should use the Builder design pattern when:
When object creation algorithms should be decoupled from the system, and multiple representations of creation algorithms are required. This decoupling is useful as you can add new creation functionality to your system without affecting the core code. You also get control over the creation process at runtime with this approach.
Let´s see some code:
public abstract class Builder {
public abstract void buildA();
public abstract void buildB();
public abstract void buildC();
public abstract Product getProduct();
}
public class ConcreteBuilder extends Builder {
private final Product product = new Product();
@Override
public void buildA() { this.product.setA("A"); }
@Override
public void buildB() { this.product.setB("B"); }
@Override
public void buildC() { this.product.setC("C"); }
@Override
public Product getProduct() { return this.product; }
}
public class Product {
public String a;
public String b;
public String c;
public String getA() { return a; }
public void setA(String a) { this.a = a; }
public String getB() { return b; }
public void setB(String b) { this.b = b; }
public String getC() { return c; }
public void setC(String c) { this.c = c; }
}
public class Director {
public Product construct(final Builder builder) {
builder.buildA();
builder.buildB();
builder.buildC();
return builder.getProduct();
}
}
public class Main {
public static void main(String[] args) {
final Director director = new Director();
final Product product = director.construct(new ConcreteBuilder());
System.out.println(String.format("Product: A = %s, B = %s, C = %s", product.getA(), product.getB(), product.getC()));
}
}
You can find the code in my GitHub repository “design-apperns“.
Not exactly matching the pattern described in the book wrote by the GoF, we can see a couple more definitions/implementations of the Builder pattern.
The first one is used to build immutable objects and, in addition, it makes easier to add new properties to your object without having a constructor with a huge number of parameters. It is similar to a fluent interface usually implemented by using method cascading or method chaining.
Let´s see an example.
public class Product {
private final String a;
private final String b;
private final String c;
private final String d;
private final String e;
private Product(ProductBuilder productBuilder) {
this.a = productBuilder.a;
this.b = productBuilder.b;
this.c = productBuilder.c;
this.d = productBuilder.d;
this.e = productBuilder.e;
}
public String getA() { return a; }
public String getB() { return b; }
public String getC() { return c; }
public String getD() { return d; }
public String getE() { return e; }
@Override
public String toString() {
return "Product{" + "a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + ", e=" + e + '}';
}
public static class ProductBuilder {
private final String a;
private final String b;
private String c; // Optional
private String d; // Optional
private String e; // Optional
public ProductBuilder(String a, String b) {
this.a = a;
this.b = b;
}
public ProductBuilder setC(final String c) {
this.c = c;
return this;
}
public ProductBuilder setD(final String d) {
this.d = d;
return this;
}
public ProductBuilder setE(final String e) {
this.e = e;
return this;
}
public Product build() {
return new Product(this);
}
}
}
public class Main {
public static void main(String[] args) {
System.out.print(new Product.ProductBuilder("A", "B")
.setC("C")
.setD("D")
.setE("E")
.build()
.toString());
}
}
You can find the example in the repository, in the builder project in the package labeled as “variant1”.
The second example, it is basically the same, but applied to POJOs to make easier to build them. It is a much more simplified version of the previous example.
Let´s see how this is:
public class Product {
private String a;
private String b;
public String getA() { return this.a; }
public Product setA(final String a) {
this.a = a;
return this;
}
public String getB() { return this.b; }
public Product setB(final String b) {
this.b = b;
return this;
}
@Override
public String toString() {
return "Product{" + "a=" + a + ", b=" + b + '}';
}
}
public class Main {
public static void main(String[] args) {
System.out.println(new Product()
.setA("A")
.setB("B")
.toString());
}
}
You can find the example in the repository, in the builder project in the package labeled as “variant2”.