π· Builder
Builder Pattern is a creational pattern to create complex objects in a multi-step process, using a construction class.
π Real-World Analogyβ
After a long year of stressful but fruitful work, you need a vacation. When planning a trip, one would usually start by defining a destination or a budget. Then, accommodations, transportation, and finally activities are being decided.
Planning a trip consists out of lots of smaller steps, allowing flexibility and customization options β making it easier to review and change details.
This step-by-step approach is perfectly resembled by the Builder Pattern β with which you can initialize your object step-by-step, add or modify information without involving a monstrous constructor.
π€ Problemβ
Imagine you have an object that needs to be set up one step at a time, with many fields, deeply nested. Typically, such a situation leads to massive constructors with long lists of parameters and a lot of hidden complexity. Or worse, it might be spread out across various parts of the client code, making the process almost impossible to follow.
Letβs stick with our previous example: planning a trip. We need to create a Trip class, but a trip can involve many elements β start date, end date, budget, cities to be visited, transportation between cities β you name it. As you can see, things can quickly become complicated.
One approach is to extend the base Trip class and create lots of subclasses to cover every possible combination of parameters. But as new subtypes are required in your software, such as a hitchhiking trip, this approach leads to an explosion of subclasses. The more options you introduce, the bigger this "zoo" of classes becomes. (Known as: subclass explosion)
An alternative solution is to keep the Trip class as one unified entity and build a massive constructor that accepts every possible parameter.
This seems like a good way to avoid the subclass problem, but it introduces new issues. Now, instead of managing multiple subclasses, youβre faced with a gigantic constructor that requires a long list of parameters β many of which might not even be necessary for the specific trip you're planning.
π‘ Solutionβ
In this scenario, the Builder Pattern can help you with both the subclass explosion and the massive constructor problems. You can construct the Trip object step-by-step, only specifying the parameters that are relevant to the specific type of trip you're planning. The builder provides a flexible, readable, and maintainable solution that allows for the creation of complex objects. For that, we need a Builder class.
To create an object using the builder pattern, individual construction steps are grouped together, such as buildDestination(), buildBudget(), etc. You can execute a series of steps to create an object using the Builder object, and you only need to call the necessary steps. Retrieve the result with the getTrip() method β it returns a very specific variant of the object, based on the steps you selected.
The construction steps might require different implementations for different objects. For instance, the budget for a weekend trip is likely to be fixed, whereas work and travel may consider monthly income when calculating trip costs. Therefore, it may be beneficial to create several different builder classes that implement the building steps differently. These builders can be used to create different variations of the object, depending on the construction process.
For example, imagine you are building a vacation package booking system using the builder pattern. One builder can construct beach vacations with hotel accommodations, while another can build skiing vacations with chalet accommodations. A third builder can create backpacking vacations with outdoor stays in a tent. This will only work if each of the Builders implements a joint interface (e.g. ITrip) as their output.
Directorβ
Abstraction and reusability are two of the main aspects of software engineering β with a Director, you can reuse construction routines.
To allow reusability and abstract away complexity, you can extract a series of calls to your builder into a separate class called Director. The Director defines the order in which to execute building steps of the Builder, while the Builder class provides the implementation.
The travel agent acts as a Director for executing travel planning steps. A Director class is optional, but it helps make things organized. All you need to do it pass the concrete Builder to the Directorβ the constructed object will be returned.
ποΈ Structureβ
- Builder Interface Declares construction steps common to all concrete builders.
- Concrete Builders provide different implementations of the common construction steps. They might produce objects that do not follow a joint interface. If they do, they return a variant of ComplexObject.
- Concrete Objects are the final result of the builder. They donβt have to belong to the same class hierarchy or interface.
- Complex Objects are created if different types of builders return concrete objects that do follow a common interface.
- Director defines an order in which construction steps are called to create specific configurations of objects. This element allows for abstraction and reusability.
- Client Code instantiates the builder and director and uses the resulting objects.
How to implementβ
- Identify similar objects within your program and their relevant building steps. Identify all the properties that are necessary for a product and the methods required to create it.
- Create an interface or an abstract base class that defines a set of methods that are common to all the previously defined objects. These methods will be implemented by the concrete builders that will be created later.
- Create a concrete builder class for each unique object representation. Each concrete builder should implement the methods defined in the interface and should be responsible for building a specific product representation.
- Create a director class that will be responsible for assembling the product representations using the concrete builders. The director class should have a set of methods that specify the order in which the steps required to build a product should be executed.
- Create an instance of the director class, and pass it to the client code that needs to construct a product. The client code should then call the methods on the director object to create the product.
- Finally, the director will return the object instance to the client code, which can then use it as needed.
- Repeat steps 5β6 as necessary to create additional product representations.
- Optionally, you can create a builder factory class that encapsulates the creation of the concrete builders, making it easier to create and manage multiple builders in your program.
Pros & Consβ
β Prosβ
β Encapsulation. Encapsulates the construction logic from the object creation process. It separates the building process from the representation of the object so that they can be easily modified independently without affecting the other.
β Extensibility. You can add new builder classes or modify the existing ones easily to create new variations of the product.
β Reusability. Object creation code and be reused β allowing the same builder to be used to construct different objects.
β Separation of Concerns. Separates the construction of the object from its representation. Promotes better design and eliminates tight coupling between classes.
β Consβ
β Might overcomplicate things. The code might become more complicated than it should be. A lot of additional classes and methods are produced for this pattern β which might not be needed. Factory Method might be a more lightweight choice to start out.
β Limited Use Cases. The Builder Pattern is not suitable for all object creation scenarios, and may not be the best design pattern for simpler objects or objects with a fixed structure.
π Relation with other patternsβ
- Factory Method is how designs usually start β over time they might evolve to Abstract Factory, Prototype or Builder.
- Abstract Factory creates families of related objects. It returns the created object immediately, whereas Builder requires additional construction steps before retrieving the final product.
- Composite Trees can be created using a Builder β construction steps can be used recursively.
- Abstract Factories, Builders and Protypes can be implemented as Singleton.