A valid alternative to contructors
Today we are going to talk about a creational pattern that in many situations can represent a useful alternative to the construction of the objects using the constructors: the Builder Pattern.
The need to introduce alternative mechanisms to those provided by Java for the creation of objects is originated from the fact that sometimes the structures are very complex and it is not always trivial to set up a well-formed constructor. Think of the cases in which the number of attributes is very high or the cases in which there are attributes that may not even be valued. The probability of making a mistake by writing the constructor by hand is very high.
The goal is to separate the creation of the object from its representation. In this way, the algorithm for creating the object is independent from the various parts that make up the object and how they are assembled.
The creation of the instances and their management are separated from each other so the program becomes simplest.
A very interesting aspect is that these mechanisms allow you to create an object step by step, checking its suitability at each step (think about when we want to build an object with data from the results of a parser) and above all it allows us to hide the control logic that would perhaps have been present in the possible manufacturer.
Let's give a definition:
The Builder Pattern is used to create instances of very complex objects with telescopic constructors in the simplest way
Let's look at the UML diagram of the Builder Pattern:
Let's analyze in detail every component:
- Product: it defines the type of object that will be generated from the Builder Pattern;
- Builder: this abstract class defines the various steps needed in order to correctly create objects. Each method is generally abstract and implementations are provided by concrete subclasses. The getProduct () method is used to return the final product. Sometimes the Builder is replaced by an interface;
- ConcreteBuilder: there may be different ConcreteBuilder concrete subclasses. These subclasses gives the mechanisms for the creation of complex objects;
- Director: the Director class controls the algorithm for the objects creation. When it is instanciated, its constructor is invoked. It contains a parameter that indicates which ConcreteBuilder has to be used for creating objects. During the creation process, the various methods of the ConcreteBuilder are called and at the end of the operations, the getProduct() method is used in order to get the final product;
Let's look a possible structure in Java
Director
public class Director { public Director(Builder builder){ builder.buildPart1(); builder.buildPart2(); builder.buildPart3(); builder.getProduct(); } }
Builder
public abstract class Builder { public abstract void buildPart1(); public abstract void buildPart2(); public abstract void buildPart3(); public abstract Product getProduct(); }
ConcreteBuilder
public class ConcreteBuilder extends Builder { private Product product; public ConcreteBuilder(){ product=new Product(); } @Override public void buildPart1() { product.setAttr1("attr1"); } @Override public void buildPart2() { product.setAttr2("attr2"); } @Override public void buildPart3() { product.setAttr3("attr3"); } @Override public Product getProduct() { return product; } }
Product
public class Product { public String attr1; public String attr2; public String attr3; public String getAttr1() { return attr1; } public void setAttr1(String attr1) { this.attr1 = attr1; } public String getAttr2() { return attr2; } public void setAttr2(String attr2) { this.attr2 = attr2; } public String getAttr3() { return attr3; } public void setAttr3(String attr3) { this.attr3 = attr3; } }
Let's have a look to a possible use of the pattern. The example that I'm going to show comes from the book Effective Java written by Joshua Bloch. We state that in this particular case, the abstract Builder class is not strictly indispensable. It can be added as an exercise without radically changing the structure presented below.
import java.util.List; public class Animal { private final String id; private String name; private String pedigreeName; private String owner; private String race; private String residence; private Boolean isVaccinated; private Boolean isChampion; private List sons; private Sex sex; private Double weight; private Double height; public Animal(String name, String pedigreeName, String id, String owner, String race, String residence, Boolean isVaccinated, Boolean isChampion, List sons, Sex sex, Double weight, Double height) { this.name = name; this.pedigreeName = pedigreeName; this.id = id; this.owner = owner; this.race = race; this.residence = residence; this.isVaccinated = isVaccinated; this.isChampion = isChampion; this.sons = sons; this.sex = sex; this.weight = weight; this.height = height; } public Animal(String id, String name, String pedigreeName) { this.name = name; this.pedigreeName = pedigreeName; this.id = id; } public Animal(String id, String owner, String race, String residence) { this.id = id; this.owner = owner; this.race = race; this.residence = residence; } public Animal(String id) { this.id = id; } public enum Sex { MALE, FEMALE } }
Now we use the pattern
import java.util.List; public final class AnimalBuilder { private String id; private String name; private String pedigreeName; private String owner; private String race; private String residence; private Boolean isVaccinated; private Boolean isChampion; private List<String> sons; private Animal.Sex sex; private Double weight; private Double height; private AnimalBuilder(String id) { this.id = id; } public static AnimalBuilder newBuilder(String id) { return new AnimalBuilder(id); } public AnimalBuilder name(String name) { this.name = name; return this; } public AnimalBuilder pedigreeName(String pedigreeName) { this.pedigreeName = pedigreeName; return this; } public AnimalBuilder owner(String owner) { this.owner = owner; return this; } public AnimalBuilder race(String race) { this.race = race; return this; } public AnimalBuilder residence(String residence) { this.residence = residence; return this; } public AnimalBuilder isVaccinated(Boolean isVaccinated) { this.isVaccinated = isVaccinated; return this; } public AnimalBuilder isChampion(Boolean isChampion) { this.isChampion = isChampion; return this; } public AnimalBuilder sons(List<String> sons) { this.sons = sons; return this; } public AnimalBuilder sex(Animal.Sex sex) { this.sex = sex; return this; } public AnimalBuilder weight(Double weight) { this.weight = weight; return this; } public AnimalBuilder height(Double height) { this.height = height; return this; } public Animal build() { return new Animal(name, pedigreeName, id, owner, race, residence, isVaccinated, isChampion, sons, sex, weight, height); } }
An object can now be instanciates as follow.
Animal pluto2=AnimalBuilder.newBuilder("0000001") .name("0000001") .pedigreeName("PlutoSecondo") .owner("Marco Rossi") .race("labrador") .residence("Via x") .isVaccinated(true) .isChampion(false) .sons(null) .sex(Animal.Sex.MALE) .weight(40.5) .height(30.0) .build();
We can find different advantages in the use of this creational pattern, in fact we can create clone objects or very similar objects minimizing the code that has to be written. The used method is similar to the one shown below, referring to the builder created before:
Animal animal3A = animalBuilder.build(); Animal animal3AClone = animalBuilder.build(); Animal animal3B = animalBuilder.sex(Animal.Sex.FEMALE).build();
Here we create two identical objects and an object similar to the previous two, but with opposite sex. A very important advantage is that of concentrating class validation in a single method and therefore obtaining almost immutable objects.
Va precisato che la versione presentata è leggermente diversa da quella presentata nel modello originale. L'unico svantaggio dell'utilizzo del pattern è il fatto che vada necessariamente definita una classe builder per ogni oggetto, aumentando nettamente il tempo di sviluppo.
Must be said that the shown version is slightly different from the one shown in the original example. The only disadvantage of using the pattern is the fact that a builder class must necessarily be defined for each object, significantly increasing the development time.
A lot of IDEs have plugin for the management of builders. Personally, I use the IntelliJ plugin Builder Generator.
By the way, in my opinion,it is always useful to have tools like this available