The Builder Design Pattern is a creational pattern that helps construct complex objects step by step. Instead of using a constructor to directly initialize an object with all its parts at once, the Builder pattern separates the construction process. It allows you to build the object incrementally and provides a more flexible way to assemble an object with various configurations, while hiding the complexity of its creation.
The Builder pattern is particularly useful when an object needs to be constructed in a specific sequence or requires many optional components that may or may not be included.
When to Use the Builder Pattern?
- When constructing objects that have a large number of optional components or configurations.
- When the object creation process involves multiple steps, and you want to abstract the complexity.
- When you want to create various representations of an object (e.g., cars with different features or options).
- When you need to ensure that an object is built in a consistent and valid state.
Key Features of the Builder Pattern
- Step-by-Step Construction: The Builder pattern allows you to build an object one part at a time. This helps manage the construction of complex objects without overwhelming the user with details all at once.
- Customizable: You can customize an object by specifying which parts should be included and how the object should be constructed, which is particularly useful for objects with many optional components.
- Fluent Interface: The Builder pattern often uses a fluent interface, which allows method calls to be chained together. This makes it easy to specify different configurations of the object in a readable way.
- Immutability: Once the object is built using the builder, it’s usually immutable, meaning that no further modifications can be made to it after construction.
- Separation of Concerns: The construction of the object is separated from its representation, allowing the construction process to be flexible while ensuring consistency in the final product.
What are the Pros and Cons?
Pros
- Improved Readability: By using a builder to construct an object step by step, the code is more readable and manageable, especially when the object has many parts or configurations.
- Flexible Configuration: You can easily create different variations of the same object by adjusting the parts being included in the construction process.
- Increased Maintainability: The Builder pattern helps to keep object creation logic isolated from other parts of the code, making it easier to maintain and modify in the future.
- Validation and Safety: The Builder pattern allows validation of the object during the construction process to ensure it is always created in a valid state.
Cons
- More Code: Implementing the Builder pattern requires additional classes (Builder, Product, Director), which might introduce more complexity than needed for simple objects.
- Not Suitable for Simple Objects: If an object’s construction process is straightforward and doesn’t involve many parts or optional configurations, the Builder pattern may be overkill and add unnecessary overhead.
Example: Vehicle Construction
Consider the construction of a custom vehicle. In a factory, a vehicle (car, truck, etc.) can be built with a variety of components such as the engine type, color, wheels, and additional features like sunroofs, GPS, etc. The process of constructing this vehicle involves several steps, and the final product may vary depending on the selected options.
In this example
- The Product is the vehicle and is the complex object being created.
- The Builder construct and assembles parts of the product by implementing the Builder interface
- The Director understands the step by step process of creating a vehicle through the use of a builder.
Class Diagram
Implementation in C#
Let’s implement the Builder Design Pattern for vehicle construction in C#.
public class Vehicle(string type)
{
public string Type { get; init; } = type;
public string? Engine { get; private set; }
public string? Chassis { get; private set; }
public string? Wheels { get; private set; }
public void SetEngine(string engine) => Engine = engine;
public void SetChassis(string chassis) => Chassis = chassis;
public void SetWheels(string wheels) => Wheels = wheels;
///
/// Get the details of what this vehicle has been setup with
///
public void ShowDetails()
{
Console.WriteLine($"Vehicle Type: {Type}");
Console.WriteLine($" Chassis : {Chassis}");
Console.WriteLine($" Engine : {Engine}");
Console.WriteLine($" Wheels: {Wheels}");
}
}
///
/// Interface to determine the configuration sets that must be implemented for a vehicle builder
///
public interface IVehicleBuilder
{
Vehicle Vehicle { get; }
IVehicleBuilder SetChassis();
IVehicleBuilder SetEngine();
IVehicleBuilder SetWheels();
}
///
/// Builds a family car
///
public class FamilyCarBuilder : IVehicleBuilder
{
public Vehicle Vehicle { get; init; } = new Vehicle("Family Car");
public IVehicleBuilder SetChassis()
{
Vehicle.SetChassis("5 Door");
return this;
}
public IVehicleBuilder SetEngine()
{
Vehicle.SetEngine("V6");
return this;
}
public IVehicleBuilder SetWheels()
{
Vehicle.SetWheels("Alloy");
return this;
}
}
///
/// Builds a sports car
///
public class SportsCarBuilder : IVehicleBuilder
{
public Vehicle Vehicle { get; init; } = new Vehicle("Family Car");
public IVehicleBuilder SetChassis()
{
Vehicle.SetChassis("Sports 5 Door");
return this;
}
public IVehicleBuilder SetEngine()
{
Vehicle.SetEngine("V8");
return this;
}
public IVehicleBuilder SetWheels()
{
Vehicle.SetWheels("Sports Alloy");
return this;
}
}
public class VehicleDirector
{
///
/// Construct a vehicle using a step by step process
///
/// The specific vehcile builder that the director will produce a vehicle with
public void Construct(IVehicleBuilder builder)
{
Console.WriteLine("Vehicle director constructing a vehicle...");
builder
.SetChassis()
.SetEngine()
.SetWheels();
}
}
// Main Program
class Program
{
static void Main(string[] args)
{
IVehicleBuilder builder;
// Only one director is needed because all vehicles are setup in the same way.
VehicleDirector vehicleDirector = new();
// Use the director to construct a family car through the family car builder.
builder = new FamilyCarBuilder();
vehicleDirector.Construct(builder);
builder.Vehicle.ShowDetails();
// Use the director to construct a sports car through the family car builder.
builder = new SportsCarBuilder();
vehicleDirector.Construct(builder);
builder.Vehicle.ShowDetails();
}
}
Here is the output from the application
Vehicle director constructing a vehicle...
Vehicle Type: Family Car
Chassis : 5 Door
Engine : V6
Wheels: Alloy
Vehicle director constructing a vehicle...
Vehicle Type: Family Car
Chassis : Sports 5 Door
Engine : V8
Wheels: Sports Alloy
Explanation of the Code
- Vehicle Class: This represents the product being built. It contains properties such as the engine, chassis and wheels, which are configured by the builder.
- FamilyCarBuilder & SportsCarBuilder Class: These are the builder classes that set the specific details of the vehicle. Each method sets a part of the vehicle (engine, chassis, etc.) and returns the builder itself to allow method chaining.
- VehicleDirector Class (Optional): The director helps create a standard step by step approach for how each vehicle is created.
- Client Code: In the
Main
method, we show how to create different vehicles using the builder pattern, such as a pre-defined family car and a sports car.
Summary
The Builder Design Pattern is a powerful tool for constructing complex objects, especially when they have many optional parts or configurations. In the vehicle construction example, the builder lets you gradually configure various aspects of the vehicle, ensuring flexibility while maintaining consistency. This pattern is particularly useful when the object creation process is intricate, allowing you to keep your code clean and maintainable.