Skip to main content

Builder

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Problem

Imagine a very complex object that requires lots of configuration parameters.

Potential Solutions:

  1. Many parameters could be optional. One simple solution is to make an abstract base class, and make many variants subclasses that extend the base class. The drawback is that there may be too many subclasses.
  2. A large constructor with many (optional) parameters. Resulting in very ugly code.

Solution

  • Extract object construction code to separate objects called builders
  • Execute a series of steps on a builder object to create an object, but not all steps need to be run.
  • Some construction steps require different implementation, so you need to create several different builder classes that implement the same set of building steps.
  • Optional: extract the series of calls to the builder steps to a Director class. Director defines the order of building steps execution. This can hide building details from client.
  1. Builder interface declares product construction steps that are common to all types of builders.
  2. Concrete Builders provide different implementations of the construction steps. Concrete builders may produce products that don’t follow the common interface.
  3. Products are resulting objects. Products constructed by different builders don’t have to belong to the same class hierarchy or interface.
  4. The Director class defines the order in which to call construction steps, so you can create and reuse specific configurations of products. See code
  5. The Client must associate one of the builder objects with the director.

Sample Code

Builders

/**
* Builder interface defines all possible ways to configure a product.
*/
public interface Builder {
void setCarType(CarType type);

void setSeats(int seats);

void setEngine(Engine engine);

void setTransmission(Transmission transmission);

void setTripComputer(TripComputer tripComputer);

void setGPSNavigator(GPSNavigator gpsNavigator);
}

/**
* Concrete builders implement steps defined in the common interface.
*/
public class CarBuilder implements Builder {
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;

public void setCarType(CarType type) {
this.type = type;
}

@Override
public void setSeats(int seats) {
this.seats = seats;
}

@Override
public void setEngine(Engine engine) {
this.engine = engine;
}

@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}

@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}

@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}

public Car getResult() {
return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}

/**
* Unlike other creational patterns, Builder can construct unrelated products,
* which don't have the common interface.
*
* In this case we build a user manual for a car, using the same steps as we
* built a car. This allows to produce manuals for specific car models,
* configured with different features.
*/
public class CarManualBuilder implements Builder {
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;

@Override
public void setCarType(CarType type) {
this.type = type;
}

@Override
public void setSeats(int seats) {
this.seats = seats;
}

@Override
public void setEngine(Engine engine) {
this.engine = engine;
}

@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}

@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}

@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}

public Manual getResult() {
return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}

Products

/**
* Car is a product class.
*/
public class Car {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;
private double fuel = 0;

public Car(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
if (this.tripComputer != null) {
this.tripComputer.setCar(this);
}
this.gpsNavigator = gpsNavigator;
}

public CarType getCarType() {
return carType;
}

public double getFuel() {
return fuel;
}

public void setFuel(double fuel) {
this.fuel = fuel;
}

public int getSeats() {
return seats;
}

public Engine getEngine() {
return engine;
}

public Transmission getTransmission() {
return transmission;
}

public TripComputer getTripComputer() {
return tripComputer;
}

public GPSNavigator getGpsNavigator() {
return gpsNavigator;
}
}

public enum CarType {
CITY_CAR, SPORTS_CAR, SUV
}

/**
* Car manual is another product. Note that it does not have the same ancestor
* as a Car. They are not related.
*/
public class Manual {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;

public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
this.gpsNavigator = gpsNavigator;
}

public String print() {
String info = "";
info += "Type of car: " + carType + "\n";
info += "Count of seats: " + seats + "\n";
info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
info += "Transmission: " + transmission + "\n";
if (this.tripComputer != null) {
info += "Trip Computer: Functional" + "\n";
} else {
info += "Trip Computer: N/A" + "\n";
}
if (this.gpsNavigator != null) {
info += "GPS Navigator: Functional" + "\n";
} else {
info += "GPS Navigator: N/A" + "\n";
}
return info;
}
}

Components

public class Engine {
private final double volume;
private double mileage;
private boolean started;

public Engine(double volume, double mileage) {
this.volume = volume;
this.mileage = mileage;
}

public void on() {
started = true;
}

public void off() {
started = false;
}

public boolean isStarted() {
return started;
}

public void go(double mileage) {
if (started) {
this.mileage += mileage;
} else {
System.err.println("Cannot go(), you must start engine first!");
}
}

public double getVolume() {
return volume;
}

public double getMileage() {
return mileage;
}
}

public class GPSNavigator {
private String route;

public GPSNavigator() {
this.route = "221b, Baker Street, London to Scotland Yard, 8-10 Broadway, London";
}

public GPSNavigator(String manualRoute) {
this.route = manualRoute;
}

public String getRoute() {
return route;
}
}

/**
* Just another feature of a car.
*/
public enum Transmission {
SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}

public class TripComputer {

private Car car;

public void setCar(Car car) {
this.car = car;
}

public void showFuelLevel() {
System.out.println("Fuel level: " + car.getFuel());
}

public void showStatus() {
if (this.car.getEngine().isStarted()) {
System.out.println("Car is started");
} else {
System.out.println("Car isn't started");
}
}
}

Director

/**
* Director defines the order of building steps. It works with a builder object
* through common Builder interface. Therefore it may not know what product is
* being built.
*/
public class Director {

public void constructSportsCar(Builder builder) {
builder.setCarType(CarType.SPORTS_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(3.0, 0));
builder.setTransmission(Transmission.SEMI_AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}

public void constructCityCar(Builder builder) {
builder.setCarType(CarType.CITY_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(1.2, 0));
builder.setTransmission(Transmission.AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}

public void constructSUV(Builder builder) {
builder.setCarType(CarType.SUV);
builder.setSeats(4);
builder.setEngine(new Engine(2.5, 0));
builder.setTransmission(Transmission.MANUAL);
builder.setGPSNavigator(new GPSNavigator());
}
}

Client Demo

public class Demo {

public static void main(String[] args) {
Director director = new Director();

// Director gets the concrete builder object from the client
// (application code). That's because application knows better which
// builder to use to get a specific product.
CarBuilder builder = new CarBuilder();
director.constructSportsCar(builder);

// The final product is often retrieved from a builder object, since
// Director is not aware and not dependent on concrete builders and
// products.
Car car = builder.getResult();
System.out.println("Car built:\n" + car.getCarType());


CarManualBuilder manualBuilder = new CarManualBuilder();

// Director may know several building recipes.
director.constructSportsCar(manualBuilder);
Manual carManual = manualBuilder.getResult();
System.out.println("\nCar manual built:\n" + carManual.print());
}

}

Limitation

Reference