In this blog post I’m going to explain how to construct a builder for constructing objects. To be clear from the beginning, what I’m going to describe is not the official builder pattern, however, in practice this is often referred to as the builder pattern nevertheless.
A builder is used to construct complex objects with many constructor parameters where some of them are optional. An alternative to handle optional parameters is the telescoping constructor pattern. For each possibility an object can be constructed, a constructor is provided. This has the drawback that it blows up the code and it is quite hard to read. From time to time, this is seen in practice and can be eliminated by bringing a builder into action. A second alternative is the JavaBeans pattern, in which a parameter-less constructor is called whereafter the properties are set via setter methods. However, this pattern has the disadvantages that the object may be in inconsistent state through its construction and it precludes the possibility to make the class immutable. Builders are by far the best approach to deal with optional parameters. Furthermore, I also have often used builders in tests to construct test objects. Builders are quite popular as it allows one to construct a fluent API.
So let’s create a builder for a Airplane object. The object has one mandatory parameter seats. Mandatory parameters usally set directly in the constructor. Furthermore, there are two optional parameters named engine and rescue and an optional List of instruments. The builder is implemented as a static nested inner class. The constructor of the Airplane is private. Note that most IDE can generate exactly this code for you!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class Airplane { | |
private final int seats; | |
private final int engine; | |
private final int rescue; | |
private final List<Instrument> instruments; | |
private Airplane(Builder builder){ | |
this.seats = builder.seats; | |
this.engine = builder.engine; | |
this.rescue = builder.rescue; | |
this.instruments = builder.instruments; | |
} | |
public static class Builder { | |
private final int seats; | |
private int engine; | |
private int rescue; | |
private List<Instrument> instruments = new ArrayList<>(); | |
public Builder(int seats){ | |
this.seats = seats; | |
} | |
public Builder withEngine(int engine) { | |
this.engine = engine; | |
return this; | |
} | |
public Builder withRescue(int rescue) { | |
this.rescue = rescue; | |
return this; | |
} | |
public Builder withInstrumentList(List<Instrument> instruments) { | |
this.instruments = instruments; | |
return this; | |
} | |
public Airplane build(){ | |
return new Airplane(this); | |
} | |
} | |
} |
An airplane object is then constructed as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<Instrument> instruments = new ArrayList<>(); | |
instruments.add(new Instrument("Altimeter")); | |
instruments.add(new Instrument("Velocity")); | |
Airplane airplane = new Airplane.Builder(4).withEngine(375).withRescue(2).withInstrumentList(instruments).build(); |
So far so good. But it would be nice if we have not to construct a list before the builder is called. It would be much more elegant if we could create the instruments list by means of the builder. In the following section we are going to dive into this problem. The goal is to construct an airplane object, that encompasses a list of instruments, as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Airplane airplane = new Airplane.Builder(4).withEngine(375).withRescue(2).addList().add().withName("Altimeter").toList().add().withName("Velocity").toList().done().build(); |
The concept behind this construct is to have parent and child builders. There is an Instrument builder that has a ListBuilder and the ListBuilder has again a Airplane builder.
The Airplane class has just slightly changed. There were only added two method called addList. The first method gets the child builder and hands over the parent builder. The second method is just a setter which is used in the child builder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class Airplane { | |
private final int seats; | |
private final int engine; | |
private final int rescue; | |
private final List<Instrument> instruments; | |
private Airplane(Builder builder){ | |
this.seats = builder.seats; | |
this.engine = builder.engine; | |
this.rescue = builder.rescue; | |
this.instruments = builder.instruments; | |
} | |
public static class Builder { | |
private final int seats; | |
private int engine; | |
private int rescue; | |
private List<Instrument> instruments; | |
public Builder(int seats){ | |
this.seats = seats; | |
} | |
public Builder withEngine(int engine) { | |
this.engine = engine; | |
return this; | |
} | |
public Builder withRescue(int rescue) { | |
this.rescue = rescue; | |
return this; | |
} | |
// get child builder and hands over parent builder | |
public Instrument.ListBuilder addList(){ | |
return new Instrument.ListBuilder().setAirplaneBuilder(this); | |
} | |
public void addList(List<Instrument> instruments){ | |
this.instruments = instruments; | |
} | |
public Airplane build(){ | |
return new Airplane(this); | |
} | |
} | |
} |
The Instrument class now comprises two nested static classes, namely the Builder and the ListBuilder. Both these classes are structurally built in the same way and in each of them there exists a parent builder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Instrument { | |
private String name; | |
private Instrument(Builder builder) { | |
this.name = builder.name; | |
} | |
public static class Builder { | |
private String name; | |
private ListBuilder listBuilder; // parent builder | |
public Builder withName(String name) { | |
this.name = name; | |
return this; | |
} | |
public Instrument build() { | |
return new Instrument(this); | |
} | |
// setter for parent builder | |
public Builder setListBuilder(ListBuilder builder) { | |
this.listBuilder = builder; | |
return this; | |
} | |
// build it and get parent builder again | |
public ListBuilder toList(){ | |
this.listBuilder.add(this.build()); | |
return this.listBuilder; | |
} | |
} | |
public static class ListBuilder { | |
private List<Instrument> instruments = new ArrayList<>(); | |
private Airplane.Builder airplaneBuilder; // parent builder | |
public ListBuilder add(Instrument instrument){ | |
this.instruments.add(instrument); | |
return this; | |
} | |
public List<Instrument> build(){ | |
return this.instruments; | |
} | |
// get child builder and hands over parent builder | |
public Instrument.Builder add() { | |
return new Instrument.Builder().setListBuilder(this); | |
} | |
// setter for parent builder | |
public ListBuilder setAirplaneBuilder(Airplane.Builder builder){ | |
this.airplaneBuilder = builder; | |
return this; | |
} | |
// build it and get parent builder again | |
public Airplane.Builder done() { | |
this.airplaneBuilder.addList(this.build()); | |
return airplaneBuilder; | |
} | |
} | |
} |
With the help of generics and reflections, these builders could be constructed more generically. Maybe I will explain this some when later.