21
NovBuilder Design Pattern
Builder Design Pattern
Builder Design Pattern is an important design pattern in C#, Java, Python, and many other languages that allows you to develop complex objects step by step. It lets you build a complicated item by separating the creation process from the representation, allowing you to generate several representations of the same type of object. This pattern gives a straightforward way to build an object, especially when the process involves multiple steps or distinct configurations.
In this design pattern tutorial, we will look at the Builder Design Pattern, explaining what it is, when to use it, and how to implement it using examples. We will begin by studying "What is the Builder Design Pattern?"
What is a Builder Pattern?
- The Builder pattern is an object-oriented design pattern used to build complex objects step by step.
- It differentiates between the construction process and the representation, allowing the same construction process to generate many representations.
- A builder class typically has methods for configuring the various components of the product and a final Build function that creates the fully built item.
When to use the builder pattern?
- Complex Object Construction: When an object requires sophisticated initialization or building that includes numerous processes or configurations.
- Multiple Representations: When a single construction process must produce multiple representations or configurations of an entity.
- Immutability: When you want to make immutable objects with different settings.
- Code Readability and Maintainability: Encapsulate the building logic and separate it from the object representation to improve code readability and maintainability.
Builder Pattern - UML Diagram & Implementation
The UML class diagram for the implementation of the builder design pattern is given below:
The classes, interfaces, and objects in the above UML class diagram are as follows:
1. Builder
- Role: Defines the abstract interface for manufacturing product pieces.
- Responsibilities: Provide methods for building each component of the product and, in most cases, a way to retrieve the finished result.
Example
public interface IHouseBuilder
{
void BuildWalls();
void BuildRoof();
void BuildDoors();
void BuildWindows();
House GetResult();
}
Explanation
The ConcreteHouseBuilder class implements the IHouseBuilder interface and creates a House object with specific properties, such as brick walls, a tile roof, wooden doors, and glass windows. It has methods for building each component of the house and a GetResult method that returns the final House object.
2. ConcreteBuilder
- Role: Implements the Builder interface for creating and assembling Product components.
- Responsibilities: Contains the precise implementation for each construction phase and the finished result.
Example
public class ConcreteHouseBuilder : IHouseBuilder
{
private House _house = new House();
public void BuildWalls() { _house.Walls = "Brick"; }
public void BuildRoof() { _house.Roof = "Tile"; }
public void BuildDoors() { _house.Doors = "Wooden"; }
public void BuildWindows() { _house.Windows = "Glass"; }
public House GetResult() { return _house; }
}
Explanation
The ConcreteHouseBuilder class, which implements the IHouseBuilder interface, creates a house object with brick walls, a tile roof, wooden doors, and glass windows. It also includes a method for returning the completed house.
3. Product
- Role: The complex thing under construction.
- Responsibilities: Represents the end product of the creation process, which is often a class with numerous attributes that may be configured using the builder.
Example
public class House
{
public string Walls { get; set; }
public string Roof { get; set; }
public string Doors { get; set; }
public string Windows { get; set; }
public override string ToString()
{
return $"House with {Walls} walls, {Roof} roof, {Doors} doors, and {Windows} windows.";
}
}
Explanation
The House class represents a house and has attributes for walls, roofs, doors, and windows that are described as strings. It overrides the ToString method and returns a descriptive string of the house's properties.
4. Director
Example
public class HouseDirector
{
private IHouseBuilder _builder;
public HouseDirector(IHouseBuilder builder)
{
_builder = builder;
}
public void ConstructHouse()
{
_builder.BuildWalls();
_builder.BuildRoof();
_builder.BuildDoors();
_builder.BuildWindows();
}
}
Explanation
The HouseDirector class manages the construction of a House by utilizing an IHouseBuilder instance. It describes the sequence of building processes in its ConstructHouse technique to ensure that the house is created in the proper order. This distinction enables different builders to be employed interchangeably, resulting in diverse styles of homes.
How Does the Builder Pattern Work?
Example
public class House
{
public string Walls { get; set; }
public string Roof { get; set; }
public string Doors { get; set; }
public string Windows { get; set; }
public override string ToString()
{
return $"House with {Walls} walls, {Roof} roof, {Doors} doors, and {Windows} windows.";
}
}
public interface IHouseBuilder
{
void BuildWalls();
void BuildRoof();
void BuildDoors();
void BuildWindows();
House GetResult();
}
public class ConcreteHouseBuilder : IHouseBuilder
{
private House _house = new House();
public void BuildWalls() { _house.Walls = "Brick"; }
public void BuildRoof() { _house.Roof = "Tile"; }
public void BuildDoors() { _house.Doors = "Wooden"; }
public void BuildWindows() { _house.Windows = "Glass"; }
public House GetResult() { return _house; }
}
public class HouseDirector
{
private IHouseBuilder _builder;
public HouseDirector(IHouseBuilder builder)
{
_builder = builder;
}
public void ConstructHouse()
{
_builder.BuildWalls();
_builder.BuildRoof();
_builder.BuildDoors();
_builder.BuildWindows();
}
}
public class Program
{
public static void Main()
{
IHouseBuilder builder = new ConcreteHouseBuilder();
HouseDirector director = new HouseDirector(builder);
director.ConstructHouse();
House house = builder.GetResult();
System.Console.WriteLine(house);
}
}
class House:
def __init__(self):
self.walls = None
self.roof = None
self.doors = None
self.windows = None
def __str__(self):
return f"House with {self.walls} walls, {self.roof} roof, {self.doors} doors, and {self.windows} windows."
class HouseBuilder:
def build_walls(self): pass
def build_roof(self): pass
def build_doors(self): pass
def build_windows(self): pass
def get_result(self): pass
class ConcreteHouseBuilder(HouseBuilder):
def __init__(self):
self.house = House()
def build_walls(self):
self.house.walls = "Brick"
def build_roof(self):
self.house.roof = "Tile"
def build_doors(self):
self.house.doors = "Wooden"
def build_windows(self):
self.house.windows = "Glass"
def get_result(self):
return self.house
class HouseDirector:
def __init__(self, builder):
self.builder = builder
def construct_house(self):
self.builder.build_walls()
self.builder.build_roof()
self.builder.build_doors()
self.builder.build_windows()
if __name__ == "__main__":
builder = ConcreteHouseBuilder()
director = HouseDirector(builder)
director.construct_house()
house = builder.get_result()
print(house)
class House {
public String walls;
public String roof;
public String doors;
public String windows;
@Override
public String toString() {
return "House with " + walls + " walls, " + roof + " roof, " + doors + " doors, and " + windows + " windows.";
}
}
interface HouseBuilder {
void buildWalls();
void buildRoof();
void buildDoors();
void buildWindows();
House getResult();
}
class ConcreteHouseBuilder implements HouseBuilder {
private House house = new House();
public void buildWalls() { house.walls = "Brick"; }
public void buildRoof() { house.roof = "Tile"; }
public void buildDoors() { house.doors = "Wooden"; }
public void buildWindows() { house.windows = "Glass"; }
public House getResult() { return house; }
}
class HouseDirector {
private HouseBuilder builder;
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
public void constructHouse() {
builder.buildWalls();
builder.buildRoof();
builder.buildDoors();
builder.buildWindows();
}
}
public class Main {
public static void main(String[] args) {
HouseBuilder builder = new ConcreteHouseBuilder();
HouseDirector director = new HouseDirector(builder);
director.constructHouse();
House house = builder.getResult();
System.out.println(house);
}
}
class House {
constructor() {
this.walls = null;
this.roof = null;
this.doors = null;
this.windows = null;
}
toString() {
return `House with ${this.walls} walls, ${this.roof} roof, ${this.doors} doors, and ${this.windows} windows.`;
}
}
class HouseBuilder {
buildWalls() {}
buildRoof() {}
buildDoors() {}
buildWindows() {}
getResult() {}
}
class ConcreteHouseBuilder extends HouseBuilder {
constructor() {
super();
this.house = new House();
}
buildWalls() { this.house.walls = "Brick"; }
buildRoof() { this.house.roof = "Tile"; }
buildDoors() { this.house.doors = "Wooden"; }
buildWindows() { this.house.windows = "Glass"; }
getResult() { return this.house; }
}
class HouseDirector {
constructor(builder) {
this.builder = builder;
}
constructHouse() {
this.builder.buildWalls();
this.builder.buildRoof();
this.builder.buildDoors();
this.builder.buildWindows();
}
}
const builder = new ConcreteHouseBuilder();
const director = new HouseDirector(builder);
director.constructHouse();
const house = builder.getResult();
console.log(house.toString());
// Product
class House {
walls: string | null = null;
roof: string | null = null;
doors: string | null = null;
windows: string | null = null;
toString(): string {
return `House with ${this.walls} walls, ${this.roof} roof, ${this.doors} doors, and ${this.windows} windows.`;
}
}
// Builder Interface
interface IHouseBuilder {
buildWalls(): void;
buildRoof(): void;
buildDoors(): void;
buildWindows(): void;
getResult(): House;
}
// Concrete Builder
class ConcreteHouseBuilder implements IHouseBuilder {
private house: House = new House();
buildWalls(): void {
this.house.walls = "Brick";
}
buildRoof(): void {
this.house.roof = "Tile";
}
buildDoors(): void {
this.house.doors = "Wooden";
}
buildWindows(): void {
this.house.windows = "Glass";
}
getResult(): House {
return this.house;
}
}
// Director
class HouseDirector {
private builder: IHouseBuilder;
constructor(builder: IHouseBuilder) {
this.builder = builder;
}
constructHouse(): void {
this.builder.buildWalls();
this.builder.buildRoof();
this.builder.buildDoors();
this.builder.buildWindows();
}
}
// Usage
const builder: IHouseBuilder = new ConcreteHouseBuilder();
const director: HouseDirector = new HouseDirector(builder);
director.constructHouse();
const house: House = builder.getResult();
console.log(house.toString());
Explanation
This example uses the Builder pattern, with House as the product class, IHouseBuilder as the builder interface, ConcreteHouseBuilder as the concrete builder that implements the construction phases, and HouseDirector directing the building process. The Program class builds a house and prints its description.
Output
House with Brick walls, Tile roof, Wooden doors, and Glass windows.
Advantages of the Builder Pattern:
- Separation of Concerns: Separates the building logic of complex objects from their representation.
- Flexible construction: Allows for multiple representations of a product using the same method.
- Readability and Maintenance: Improves code readability and maintainability by encapsulating the construction process.