In the earlier tutorials we have seen the factory method and the abstract factory pattern. In this tutorial we look at another creational pattern called the Builder Design pattern.
The Builder Design pattern specifies a builder class that creates multiple objects in a step by step manner. The client uses the builder to build the multiple objects and is itself not concerned with how or which object is created. The client has reference to the interface of the objects created and not the actual implementation. The factory method delegated the object creation to a subclass, the abstract factory pattern provided a factory using which the client can create multiple objects, and the Builder pattern goes a step further in that the client does not have to concern itself with creating the objects. We will use the same Car Builder example to understand the builder pattern.
Lets first look at the various classes
We define a Car interface that has setters for Objects that build it. We have implemented a Ferrari car. Note that the setters take an interface an not a concrete implementation.
public interface Car { public void setInsurance(Insurance insurance); public void setMaintenance(Maintenance maintenance); public void setBodyType(BodyType bodyType); public void setEngineType(EngineType engineType); } public class Ferrari implements Car { private String name; private Maintenance maintenance; private Insurance insurance; private EngineType engineType; private BodyType bodyType; public Ferrari(String name) { this.name = name; } public void setInsurance(Insurance insurance) { this.insurance = insurance; } public void setMaintenance(Maintenance maintenance) { this.maintenance = maintenance; } public void setBodyType(BodyType bodyType) { this.bodyType = bodyType; } public void setEngineType(EngineType engineType) { this.engineType = engineType; } }
The Insurance Interface is implemented by two kinds of insurance schemes, partial and complete.
public interface Insurance { } public class PartialInsurance implements Insurance { String name; public PartialInsurance(String name) { this.name = name; } } public class CompleteInsurance implements Insurance { String name; public CompleteInsurance(String name) { this.name = name; } }
The Maintenance is defined as an interface and implemented by two types of maintenance schemes, basic and comprehensive.
public interface Maintenance { } public class BasicMaintenance implements Maintenance { String name; public BasicMaintenance(String name) { this.name = name; } } public class ComprehensiveMaintenance implements Maintenance { String name; public ComprehensiveMaintenance(String name) { this.name = name; } }
The BodyType is defined as an interface which is implemented by a convertible and a sedan.
public interface BodyType { } public class Convertible implements BodyType { String name; public Convertible(String name) { this.name = name; } } public class Sedan implements BodyType { String name; public Sedan(String name) { this.name = name; } }
We define two engine types, V4 and V6
public interface EngineType { } public class V4 implements EngineType { String name; public V4(String name) { this.name = name; } } public class V6 implements EngineType { String name; public V6(String name) { this.name = name; } }
We code to interfaces since then its possible to add more implementations. Lets look at how we would do this in a non design pattern world. The client creates an instance of a car. It then creates instances of each object (Body Type, Engine Type, Insurance and Maintenance) and assigns it to the car.
public class ClientA { public static void main(String[] args) { ClientA client = new ClientA(); client.buildFerrari(); client.buildMazda(); } private void buildFerrari() { Ferrari ferrari = new Ferrari("ferrari"); ferrari.setBodyType(new Convertible("Conv1")); ferrari.setEngineType(new V4("v4")); ferrari.setMaintenance(new BasicMaintenance("basic")); ferrari.setInsurance(new CompleteInsurance("insA")); } private void buildMazda() { Mazda mazda = new Mazda("mazda"); mazda.setBodyType(new Sedan("sedan")); mazda.setEngineType(new V6("v6")); mazda.setMaintenance(new ComprehensiveMaintenance("compA")); mazda.setInsurance(new PartialInsurance("insB")); } }
Lets look at the problems in this approach and how Builder Pattern can solve them
- The client needs to know how to build the cars. If there is a change in the way a car is built then all the clients would need to implement the change (If an insurance policy changes then all clients have to change their code). The Builder pattern solves the problem by delegating the process of creating the objects in a separate class. The Builder class not only creates the objects but also makes sure that they are created in a specified order
- We can use the abstract factory pattern, but the number of combinations can increase at a fast place and we would end up with lot of factories. We can design a Builder that can take in a list of initialization parameters and create the required objects in a step by step fashion.
Hers’s a class diagram using the Builder Pattern.
The classes
Builders
public interface CarBuilder { public Car buildCar(); public Car buildCarParameterized(String... initializers); }
public class FerrariCarBuilder implements CarBuilder { @Override public Car buildCar() { Ferrari ferrari = new Ferrari("ferrari"); ferrari.setBodyType(new Convertible("Conv1")); ferrari.setEngineType(new V4("v4")); ferrari.setMaintenance(new BasicMaintenance("basic")); ferrari.setInsurance(new CompleteInsurance("insA")); return ferrari; } @Override public Car buildCarParameterized(String... initializers) { return null; } }
public class MazdaCarBuilder implements CarBuilder { @Override public Car buildCar() { Mazda mazda = new Mazda("mazda"); mazda.setBodyType(new Sedan("sedan")); mazda.setEngineType(new V6("v6")); mazda.setMaintenance(new ComprehensiveMaintenance("compA")); mazda.setInsurance(new PartialInsurance("insB")); return mazda; } /** * This method shows a partially implemented build method that uses * parameters to build objects * * @param initializers * @return */ public Car buildCarParameterized(String... initializers) { Mazda mazda = new Mazda("mazda"); for (String initializer : initializers) { String[] tokens = initializer.split("-"); if (tokens[0].equals("bodyType")) { if (tokens[1].equals("sedan")) mazda.setBodyType(new Sedan(tokens[2])); else if (tokens[1].equals("convertible")) mazda.setBodyType(new Convertible(tokens[2])); } else if (tokens[0].equals("engineType")) { // code for engine type } // code for maintenance and insurance } return mazda; } }
The Client
public class CarBuilderClient { static CarBuilder builder; static Car car; public static void main(String[] args) { CarBuilderClient client = new CarBuilderClient(); builder = new FerrariCarBuilder(); client.buildCar(builder); builder = new MazdaCarBuilder(); client.buildCarParameterized(builder, "bodyType-sedan-mazdasedan", "engineType-V4-mazdav4"); } private void buildCar(CarBuilder builder) { car = builder.buildCar(); } private void buildCarParameterized(CarBuilder builder, String... parameters) { builder.buildCarParameterized(parameters); } }
Here’s an explanation of the classes
CarBuilder – The CarBuilder is the builder that is the heart of this pattern. Its job is to create the objects that form part of the car. Our Builder has two methods, or two ways to instantiate car objects. The first method called buildCar creates the objects using a predefined sequence and type. The second method is a parameterized builder. We pass in multiple Strings. Each string defines the type of object to be created. For example, “bodyType-sedan-mazdasedan” creates a BodyType Sedan named mazdasedan. The advantage of the parameterised builder is that the client can specify the concrete type to be created and also the sequence in which they should be created
CarBuildClient – The Client describes how the builder can be used. We create the objects for Ferrari using the FerrariCarBuilder and the Mazda using parameterized method of MazdaCarBuilder.
Lets see the advantages of the builder method