TypeScript Builder Pattern Example

1. Definition

The Builder pattern is a creational design pattern that lets you construct complex objects step by step. It allows you to produce different types and representations of an object using the same construction code.

2. Problem Statement

Creating an object with many optional components or configurations can lead to an explosion of constructors or a highly mutable object susceptible to incorrect setup. This can make the system error-prone and hard to maintain.

3. Solution

The Builder pattern addresses this by providing a clear and flexible way to create complex objects. The pattern divides the construction process from the representation so that the same construction process can produce different representations.

4. Real-World Use Cases

1. Building a complex document, like an RTF or a PDF file.

2. Constructing meals with multiple courses and options.

3. Assembling custom PCs with different components based on user preferences.

5. Implementation Steps

1. Create a Product class that has the final object representation.

2. Define a Builder interface that specifies methods for creating parts of the Product.

3. Implement concrete builders for different representations of the product.

4. Create a Director class that constructs the product using the builder's methods.

6. Implementation in TypeScript

// Step 1: Create a Product class
class Product {
    private parts: string[] = [];
    add(part: string): void {
        this.parts.push(part);
    }
    show(): string {
        return this.parts.join(", ");
    }
}
// Step 2: Define the Builder interface
interface Builder {
    buildPartA(): void;
    buildPartB(): void;
}
// Step 3: Implement concrete builders
class ConcreteBuilder1 implements Builder {
    private product = new Product();
    buildPartA(): void {
        this.product.add("PartA1");
    }
    buildPartB(): void {
        this.product.add("PartB1");
    }
    getResult(): Product {
        return this.product;
    }
}
class ConcreteBuilder2 implements Builder {
    private product = new Product();
    buildPartA(): void {
        this.product.add("PartA2");
    }
    buildPartB(): void {
        this.product.add("PartB2");
    }
    getResult(): Product {
        return this.product;
    }
}
// Step 4: Create the Director class
class Director {
    construct(builder: Builder): void {
        builder.buildPartA();
        builder.buildPartB();
    }
}
// Usage:
const director = new Director();
const builder1 = new ConcreteBuilder1();
director.construct(builder1);
console.log("Product 1: " + builder1.getResult().show());
const builder2 = new ConcreteBuilder2();
director.construct(builder2);
console.log("Product 2: " + builder2.getResult().show());

Output:

Product 1: PartA1, PartB1
Product 2: PartA2, PartB2

Explanation:

In the provided TypeScript implementation, the Product class represents the complex object. 

The Builder interface defines the steps to create parts of the product. Concrete builders (ConcreteBuilder1 and ConcreteBuilder2) provide different implementations for these steps. 

The Director class defines the sequence in which to execute the building steps. By using the Director with different builders, we get different representations of the product. This demonstrates the flexibility and separation of concerns that the Builder pattern provides.

7. When to use?

The Builder pattern is particularly useful when:

1. An object must be created with many optional components or configurations.

2. The process of constructing an object should be separated from its representation.

3. The system needs to allow different representations for the object being built.


Comments