Builder is a creational design pattern. Builder is used when we have a lot of fields in our class and we do not want to create a lot of constructors and also we do not want another developer to have an access to setters. There are several types of builder pattern. One of them is a clasic builder. We use it when we do not want at all to give another developer ability for setting fields while object creating. Another type of builder is builder with internal class. We use it when we want to give another developer ability for setting fields while object creating but we do not want him to change particular fields of object after the object is created. In builder with internal class we use an object of an internal class as argument in private constructor for creating an object and setting fields. Let’s see builder with internal class in real life example. First we start with a FlightLeg class which has four private fields. First two fields are String from and String to, we will store there city where our flight starts from and city of our destination. We do not want to change these cities after object creation so we make them final. Our class has also delayed field which will be false after object creation and can be changed by setter. And finaly we have price field which also can be changed later. We can also add toString function to see our flight.
package BuilderTask;
public class FlightLeg {
private final String from;
private final String to;
private boolean delayed;
private float price;
public void setDelayed(boolean delayed) {
this.delayed = delayed;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "FlightLeg{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", delayed=" + delayed +
", price=" + price +
'}';
}
}
Now we need our internal class. We’ll call it FlightLegBuilder. It will have the same fields but with small differences. Field delayed will always be false after object creation so we can make it final. Also we will give to price a start value 0, because we will throw an exception if another developer will forget to change this value. In constructor we will declare from and to values and also give a false value to delayed. Then we create a buildPrice function which will set a value to price and will return object of FlightLegBuilder class. Finaly we create a build function which will check if another developer set a new value to price and it is greater than 0, if it’s not we will throw an IllegalStateException, and if price is greater than 0 we will pass this FlightLegBuilder object to FlightLeg constructor and will return new FlightLeg object. First let’s see how our internal class looks like.
public static class FlightLegBuilder {
private final String from;
private final String to;
private final boolean delayed;
private float price = 0;
public FlightLegBuilder(String from, String to) {
this.from = from;
this.to = to;
this.delayed = false;
}
public FlightLegBuilder buildPrice(float price) {
this.price = price;
return this;
}
public FlightLeg build() {
if(this.price <= 0) {
throw new IllegalStateException("Lack of Price!");
}
return new FlightLeg(this);
}
}
Now we need to create a constructor of FlightLeg class. We will pass to it an object of our internal class and it will set fields according to our builder object fields.
private FlightLeg(FlightLegBuilder flightLegBuilder) {
this.from = flightLegBuilder.from;
this.to = flightLegBuilder.to;
this.delayed = flightLegBuilder.delayed;
this.price = flightLegBuilder.price;
}
Let’s see our class in its entirety.
package BuilderTask;
public class FlightLeg {
private final String from;
private final String to;
private boolean delayed;
private float price;
private FlightLeg(FlightLegBuilder flightLegBuilder) {
this.from = flightLegBuilder.from;
this.to = flightLegBuilder.to;
this.delayed = flightLegBuilder.delayed;
this.price = flightLegBuilder.price;
}
public void setDelayed(boolean delayed) {
this.delayed = delayed;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "FlightLeg{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", delayed=" + delayed +
", price=" + price +
'}';
}
public static class FlightLegBuilder {
private final String from;
private final String to;
private final boolean delayed;
private float price = 0;
public FlightLegBuilder(String from, String to) {
this.from = from;
this.to = to;
this.delayed = false;
}
public FlightLegBuilder buildPrice(float price) {
this.price = price;
return this;
}
public FlightLeg build() {
if(this.price <= 0) {
throw new IllegalStateException("Lack of Price!");
}
return new FlightLeg(this);
}
}
}
Now let’s see how it works in Main. First we declare a new object of FlightLeg class which will be a flight from Wroclaw to Napoli, we put these values to FlightLegBuilder constructor, then we set a price with value 249.99 and finaly we call a build function. In next line we print our created object. Then we declare an another flight from Wroclaw to Bergamo but without setting a price.
import BuilderTask.FlightLeg;
public class Main {
public static void main(String[] args) {
FlightLeg flightToNapoli = new FlightLeg.FlightLegBuilder("Wroclaw", "Napoli").buildPrice(249.99f).build();
System.out.println(flightToNapoli);
System.out.println();
FlightLeg flightToBergamo = new FlightLeg.FlightLegBuilder("Wroclaw", "Bergamo").build();
}
}
Let’s run this program and see what happens.
FlightLeg{from=’Wroclaw’, to=’Napoli’, delayed=false, price=249.99}
Exception in thread „main” java.lang.IllegalStateException: Lack of Price!
at BuilderTask.FlightLeg$FlightLegBuilder.build(FlightLeg.java:53)
at Main.main(Main.java:10)
Process finished with exit code 1
As expected our flight to Napoli looks good but our second flight to Bergamo thrown an exception because we haven’t set a price.