Design Patterns- Creational – Builder Pattern

In 2013, Dave Hakkens a Dutch designer came up with a concept of making an open source modular smart phone named Phonebloks. By attaching individual third party components called bloks to a main board, a user can create his/her own personalized smart phone. These bloks can be snapped onto the main board like Lego bricks. As a result, instead of replacing an entire phone when say the screen is damaged or when the camera is broken or has become obsolete, the user can replace just the damaged or obsolete blok.

Lets assume that you are required to write a software to build these smart phones. Customers would give their initial configuration of their personalized Phoneblok, For example:- Some customers would want a big Nikon front facing camera instead of a big display screen, some might want an extra battery to increase the battery life, some might want a projector instead of a camera. The amount of customization the customers can make would be endless.

For simplicity, lets assume that there are 6 different bloks :-
1. Processor
2. Internal Memory
3. Battery
4. Display Screen
5. Camera
6. Projector

Among these bloks, the processor, internalMemory, battery and the displayScreen are mandatory as they are the minimum that a phoneblok needs to run.

Traditionally, programmers use the telescoping constructor pattern, in which you provide a constructor with only the required parameters, another with a single optional parameter, a third with two optional parameters and so on culminating in a constructor with all the optional parameters.

package com.codeskittles.learning.designpatterns.builder;

public class Phoneblok {

  private final String processor;
  private final String internalMemory;
  private final String battery;
  private final String displayScreen;
  private final String camera;
  private final String projector;

  public Phoneblok(
      final String processor,
      final String internalMemory,
      final String battery,
      final String displayScreen) {

    this(processor, internalMemory, battery, displayScreen, null);
  }

  public Phoneblok(
      final String processor,
      final String internalMemory,
      final String battery,
      final String displayScreen,
      final String camera) {

    this(processor, internalMemory, battery, displayScreen, camera, null);
  }

  public Phoneblok(
      final String processor,
      final String internalMemory,
      final String battery,
      final String displayScreen,
      final String camera,
      final String projector) {

    this.processor = processor;
    this.internalMemory = internalMemory;
    this.battery = battery;
    this.displayScreen = displayScreen;
    this.camera = camera;
    this.projector = projector;
  }
}

When you want to create a Phonebloc with just a projector and no camera, you call the constructor as below.

Phoneblok phoneBlokWithProjectorNoCamera =
        new Phoneblok(
            "Intel i9 10th gen",
            "16gb Samsung RAM",
            "4000mAh Battery",
            "UHD 4K Screen",
            null,
            "Elite Projector 4K");

Typically this constructor invocation will require many parameters that you don’t want to set, but you’re forced to pass a value. In the above scenario, we passed a value of null for camera. With “only” six parameters this may not seem so bad, but since code is always in a state of flux there is a possibility of new torch blok being added, this quickly gets out of hand as the number of parameters increases.

The telescoping constructor pattern works, but it is hard to write client code when there are many parameters, and harder still to read it. The client is left wondering what all those values mean and must carefully count parameters to find out. Long sequences of identically typed parameters can cause bugs and if the client accidentally reverses two such parameters, the compiler won’t complain, but the program will misbehave at runtime.

Phoneblok phoneBlokWithProjectorNoCamera =
        new Phoneblok(
            "16gb Samsung RAM", // Misplaced parameter. Sent internal Memory instead of Processor
            "Intel i9 10th gen",
            "4000mAh Battery",
            "UHD 4K Screen",
            null,
            "Elite Projector 4K");

// Compiler will not throw any errors, but the program will misbehave at runtime.

A second alternative when you have many optional parameters in a constructor is the JavaBeans pattern, in which you call a parameter-less constructor to create the object and then call setter methods to set each required parameter and each optional parameter.

package com.codeskittles.learning.designpatterns.builder;

public class Phoneblok {

  private String processor;
  private String internalMemory;
  private String battery;
  private String displayScreen;
  private String camera;
  private String projector;

  public Phoneblok() {}

  public void setProcessor(String processor) {
    this.processor = processor;
  }

  public void setInternalMemory(String internalMemory) {
    this.internalMemory = internalMemory;
  }

  public void setBattery(String battery) {
    this.battery = battery;
  }

  public void setDisplayScreen(String displayScreen) {
    this.displayScreen = displayScreen;
  }

  public void setCamera(String camera) {
    this.camera = camera;
  }

  public void setProjector(String projector) {
    this.projector = projector;
  }
}

This pattern has none of the disadvantages of the telescoping constructor pattern. It is easy to create instances and easy to read the code.

    Phoneblok phoneblok = new Phoneblok();

    phoneblok.setProcessor("Intel i9 10th gen");
    phoneblok.setInternalMemory("16gb Samsung RAM");
    phoneblok.setBattery("4000mAh Battery");
    phoneblok.setDisplayScreen("UHD 4K Screen");
    phoneblok.setProjector("Elite Projector 4K");

/* More readable. Client knows exactly what field is being set and also does not need to set other optional fields. */

Unfortunately, the JavaBeans pattern has serious disadvantages because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction. The class does not have
the option of enforcing consistency merely by checking the validity of the constructor parameters. Attempting to use an object when it’s in an inconsistent state may cause failures that are far removed from the code containing the bug and hence difficult to debug.

Builder Pattern to the rescue which combines the safety of the telescoping constructor pattern with the readability of the JavaBeans pattern.

Builder Pattern

The Builder pattern suggests that you extract the object construction code into a different class typically named as Builder. This builder is usually a static member of the Type that it builds. To create an object you would need to perform a series of steps on the builder object.

package com.codeskittles.learning.designpatterns.builder;

// Immutable class
public class Phoneblok {

  private final String processor;
  private final String internalMemory;
  private final String battery;
  private final String displayScreen;
  private final String camera;
  private final String projector;

  private Phoneblok(final Builder builder) {
    this.processor = builder.getProcessor();
    this.internalMemory = builder.getInternalMemory();
    this.battery = builder.getBattery();
    this.displayScreen = builder.getDisplayScreen();
    this.camera = builder.getCamera();
    this.projector = builder.getProjector();
  }

  // Exposed static method to return a builder object to build objects of the Phoneblok Type.
  public static Builder getBuilder() {
    return new Builder();
  }

  private static class Builder {
    private String processor;
    private String internalMemory;
    private String battery;
    private String displayScreen;
    private String camera;
    private String projector;

    public Builder processor(final String processor) {
      this.processor = processor;
      /* Returns the builder object. By doing this the object construction becomes fluent since the steps can be chained. */
      return this;
    }

    public Builder internalMemory(final String internalMemory) {
      this.internalMemory = internalMemory;
      return this;
    }

    public Builder battery(final String battery) {
      this.battery = battery;
      return this;
    }

    public Builder displayScreen(final String displayScreen) {
      this.displayScreen = displayScreen;
      return this;
    }

    public Builder camera(final String camera) {
      this.camera = camera;
      return this;
    }

    public Builder projector(final String projector) {
      this.projector = projector;
      return this;
    }

    /* Method builds the Phoneblock object. Here is where you can add any validation if required. */
    public Phoneblok build() {
      return new Phoneblok(this);
    }

    private String getProcessor() {
      return processor;
    }

    private String getInternalMemory() {
      return internalMemory;
    }

    private String getBattery() {
      return battery;
    }

    private String getDisplayScreen() {
      return displayScreen;
    }

    private String getCamera() {
      return camera;
    }

    private String getProjector() {
      return projector;
    }
  }
}

Client code will look like the below and is easy to write and more importantly easy to read. Validity checks can be introduced in the builder’s constructor and methods to identify invalid inputs as early as possible, or can also be checked with the builder’s build method is called.


    final Phoneblok bareBonesPhone =
/* Fluent API Calls */
        Phoneblok.getBuilder()
            .processor("Intel i9 10th gen")
            .internalMemory("16gb Samsung RAM")
            .battery("4000mAh Battery")
            .displayScreen("UHD 4K Screen")
            .build();
    
    final Phoneblok withProjectorAndNoCamera =
            Phoneblok.getBuilder()
                    .processor("Intel i9 10th gen")
                    .internalMemory("16gb Samsung RAM")
                    .battery("4000mAh Battery")
                    .displayScreen("UHD 4K Screen")
                    .projector("Elite Projector 4K")
                    .build();

All the code is uploaded to Github.
Co-ordinates : https://github.com/roshanvinil89/learning.git