Builder is a creational design pattern that let you produce different types and representations of an object using the same building process. Builder allows constructing complex objects step by step.
Imagine an object that requires laborious step by step initialization of many fields and nested objects. Such code is usually buried inside a constructor with lots of parameters, or worse, scattered all over the client code.
For example, let's think how to create a
House class. To build a simple house, you need to construct four walls, install a door and two windows, build a roof.
But what if you want a bigger, brighter house, with a backyard and other goodies?
The simplest solution that comes to mind is to extend the base
House class and to create subclasses to cover all combinations of the parameters. The obvious problem with this approach is a considerable number of subclasses you'll end up with. Any new parameter, such as porch style, will require growing this hierarchy even more.
On the other hand, you can create a giant constructor in the base
House class that would accept all possible parameters of resulting house. Some of these parameters will be unused most of the time. For instance, not every house has a pool. So all parameters related to a pool construction will be useless for the majority of houses.
The Builder pattern organizes all stages of object construction as separate methods in a
Now, to create a product, you will need to call several building steps in a class that implements
Builder interface. The important part here is that you don't need to call all the steps. You can use only those which are required for building a particular object.
A program could have several
Builder classes, as long as they implement the common interface. Their implementations may be absolutely different.
For example, there might be
StoneBuilder that makes all house components from stone and
WoodBuilder that uses wood. You pass a desired builder object into the client code and get a house built either from stone or wood. And of course, it would work only if client code interacts with builders using their common interface.
You can go even further and extract the whole building process to a special class called
Director. In this case, the
Director will be defining the order of building steps, and a
Builder will execute them.
Builder defines the steps required to build a product.
Concrete Builders provide different implementations of the construction steps.
Builders can also provide methods for fetching the construction result. This method can't be defined inside the builder interface, because builders may produce products that don't have the common interface. But if you're dealing with products from a single hierarchy, then this method can be safely described in the base interface.
Product is an object created as a result of construction. Builders can produce products that don't belong to the same class hierarchy or interface. It is a key difference between the Builder and other creational patterns.
Director constructs products using a Builder object. Usually, client assigns a builder instance to a director just once via constructor parameters. Then director uses that single builder object for all further construction. But there's an alternative when a builder is passed to the main production method of a director.
The pattern does not strictly require a separate director class. The separate director class is handy when you have several product variations that require different construction processes. Director can encapsulate all that code inside a single class.
This example illustrates how Builder is used for the step-by-step construction of cars. The director class produces various models of cars using a different set of construction steps. Director works with a build object that was given to him. This allows you to reuse existing construction code for producing user manuals for various car models. To achieve this, you just need to a new builder class.
Therefore, the Builder design pattern allows you to create different variations of products, simply by changing the amount and order of the construction steps. Also, you can produce an entirely different type of product using the same construction code, by submitting an alternative version of a builder object.
// Builder can create different products using the same building process. class Car is Can have GPS, trip computer and various numbers of seats. Can be a city car, a sports car, or a cabriolet. class Manual is Textual representation of a car. // Builder interface defines all possible ways to configure a product. interface Builder is method setSeats(number) method setEngine(engine: Engine) method setTripComputer() method setGPS() // Concrete builders implement that interface differently. class CarBuilder implements Builder is method setSeats(number) is Tell the builder the number of seats. method setEngine(engine: Engine) is Install a given engine. method setTripComputer() is Install a trip computer. method setGPS() is Install a global positioning system. method getResult(): RealCar is Construct and return a real car. // Unlike other creational patterns, Builder can construct unrelated products, // which don't have the common interface. class CarManualBuilder implements Builder is method setSeats(number) is Document car seats features. method setEngine(engine: Engine) is Add an engine instruction. method setTripComputer() is Add a trip computer instruction. method setGPS() is Add GPS instruction. method getResult(): Manual is Get manual contents. // 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. class Director is method constructSportsCar(builder: Builder) is builder.setSeats(2) builder.setEngine(new SportEngine()) builder.setTripComputer() builder.setGPS() // 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. class Application is method makeCar is director = new Director(); CarBuilder builder = new CarBuilder(); director.constructSportsCar(builder); Car car = builder.getResult(); CarManualBuilder builder = new CarManualBuilder(); 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. Manual manual = builder.getResult();
When you have a "telescopic" constructor.
A constructor with a dozen of optional parameters is not convenient to call. You have to specify all of the parameters, even if you don't need them.
To ease the pain, one can overload a long constructor and create several shorter versions with fewer parameters. They will still call the main constructor, but pass some default values into omitted parameters.
The Builder pattern allows building objects step by step. Moreover, you can use only required steps and skip the optional ones when building a simple object.
When your code has to create different representations of one product (for example, stone and wooden houses). Construction of the product has similar steps that differ in details. Plus, although the products may be similar, they don't necessary have to have a common base class or interface.
Builder can be used to construct different products using the same building process.
Each distinct product will be represented by a separate builder class. Code that controls the construction order may live in a single director class.
When you have to build a Composite tree or another complex object.
Builder constructs products steps by step. It allows deferred or even recursive building that is mandatory when you're working with tree structures.
Builder object doesn't expose unfinished product between construction steps. Technically, the end result can be grabbed from a builder after each step. But once the result returned, no further customization over that instance can be performed by the builder object.
How to Implement
Make sure that you have the common steps of building the product, as well as variations of the steps that lead to the creation of various representations of products.
Create the Builder interface and define the production steps in it.
Create a Concrete Builder class for each of the product representations. Implement their construction steps.
Think about creating a Director class. Its methods should create different product configurations, using different steps of the same builder instance.
The client code creates both Builder and Director objects. It creates a builder instance first and then passes it either to the director's constructor or its production methods.
The client should call a production method of a Director object to begin the construction process.
The result can be obtained from the Director object only if all products have a common interface. In the opposite case, each Builder must have its own method of retrieving the result.
Pros and Cons
- Allows building products step by step.
- Allows using the same code for building different products.
- Isolates the complex construction code from a product's core business logic.
- Increases overall code complexity by creating multiple additional classes.
Relations with Other Patterns
Often, designs start out using Factory Method (less complicated, more customizable via subclasses) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, but more complex) as the designer discovers where more flexibility is needed.
Builder focuses on constructing a complex object step by step. Abstract Factory creates families of product objects (either simple or complex). Builder returns the product as a final step, but the Abstract Factory returns the result immediately.