Skip to content

Builder Design Pattern

Video Lecture

Section Video Links
Builder Pattern Builder Builder Pattern  
Builder Use Case Builder Use Case Builder Use Case  

Overview

The Builder Pattern is a creational pattern whose intent is to separate the construction of a complex object from its representation so that you can use the same construction process to create different representations.

The Builder Pattern tries to solve,

  • How can a class create different representations of a complex object?
  • How can a class that includes creating a complex object be simplified?

The Builder and Factory patterns are very similar in the fact they both instantiate new objects at runtime. The difference is when the process of creating the object is more complex, so rather than the Factory returning a new instance of ObjectA, it calls the builders' director constructor method ObjectA.construct() that goes through a more complex construction process involving several steps. Both return an Object/Product.

Terminology

  • Product: The Product being built.
  • Builder Interface: The Interface that the Concrete builder should implement.
  • Builder: Provides methods to build and retrieve the concrete product. Implements the Builder Interface.
  • Director: Has a construct() method that when called creates a customized product using the methods of the Builder.

Builder UML Diagram

Builder Pattern Overview

Source Code

  1. Client creates the Director.
  2. The Client calls the Directors construct() method that manages each step of the build process.
  3. The Director returns the product to the client or alternatively could also provide a method for the client to retrieve it later.

./src/builder/builder-concept.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Builder Concept Sample Code
class Product {
    parts: string[] = []
}

interface IBuilder {
    buildPartA(): this
    buildPartB(): this
    buildPartC(): this
    getResult(): Product
}

class Builder implements IBuilder {
    // The Concrete Builder
    product: Product

    constructor() {
        this.product = new Product()
    }

    buildPartA() {
        this.product.parts.push('a')
        return this
    }

    buildPartB() {
        this.product.parts.push('b')
        return this
    }

    buildPartC() {
        this.product.parts.push('c')
        return this
    }

    getResult() {
        return this.product
    }
}

class Director {
    // The Director, building a complex representation

    static construct() {
        'Constructs and returns the final product'
        return new Builder()
            .buildPartA()
            .buildPartB()
            .buildPartC()
            .getResult()
    }
}

// The Client
const PRODUCT1 = Director.construct()
console.log(PRODUCT1.parts)

Output

node ./dist/builder/builder-concept.js
[ 'a', 'b', 'c' ]

Builder Use Case

Using the Builder Pattern in the context of a House Builder.

There are multiple directors that can create their own complex objects.

Note that in the IglooDirector class, not all the methods of the HouseBuilder were called.

The builder can construct complex objects in any order and include/exclude whichever parts it likes.

Example UML Diagram

Builder Pattern in Context

Source Code

./src/builder/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// House Builder Example Code

import IglooDirector from './igloo-director'
import CastleDirector from './castle-director'
import HouseBoatDirector from './houseboat-director'

const IGLOO = IglooDirector.construct()
const CASTLE = CastleDirector.construct()
const HOUSEBOAT = HouseBoatDirector.construct()

console.log(IGLOO.construction())
console.log(CASTLE.construction())
console.log(HOUSEBOAT.construction())

./src/builder/igloo-director.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// A Director Class
import House from './house'
import HouseBuilder from './house-builder'

export default class IglooDirector {
    static construct(): House {
        // Note that in this IglooDirector, it has omitted the
        // set_number_of windows call since this Igloo will have
        // no windows.
        return new HouseBuilder()
            .setBuildingType('Igloo')
            .setWallMaterial('Ice')
            .setNumberDoors(1)
            .getResult()
    }
}

./src/builder/castle-director.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// A Director Class
import House from './house'
import HouseBuilder from './house-builder'

export default class CastleDirector {
    static construct(): House {
        return new HouseBuilder()
            .setBuildingType('Castle')
            .setWallMaterial('Sandstone')
            .setNumberDoors(100)
            .setNumberWindows(200)
            .getResult()
    }
}

./src/builder/houseboat-director.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// A Director Class
import House from './house'
import HouseBuilder from './house-builder'

export default class HouseBoatDirector {
    static construct(): House {
        return new HouseBuilder()
            .setBuildingType('House Boat')
            .setWallMaterial('Wood')
            .setNumberDoors(6)
            .setNumberWindows(8)
            .getResult()
    }
}

./src/builder/house-builder.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import House from './house'

interface IHouseBuilder {
    house: House
    setBuildingType(buildingType: string): this
    setWallMaterial(wallMaterial: string): this
    setNumberDoors(number: number): this
    setNumberWindows(number: number): this
    getResult(): House
}

export default class HouseBuilder implements IHouseBuilder {
    house: House

    constructor() {
        this.house = new House()
    }

    setBuildingType(buildingType: string): this {
        this.house.buildingType = buildingType
        return this
    }

    setWallMaterial(wallMaterial: string): this {
        this.house.wallMaterial = wallMaterial
        return this
    }

    setNumberDoors(number: number): this {
        this.house.doors = number
        return this
    }

    setNumberWindows(number: number): this {
        this.house.windows = number
        return this
    }

    getResult(): House {
        return this.house
    }
}

./src/builder/house.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// The Product

export default class House {
    doors = 0
    windows = 0
    wallMaterial = ''
    buildingType = ''

    construction(): string {
        return `This is a ${this.wallMaterial} ${this.buildingType} with ${this.doors} door(s) and ${this.windows} window(s).`
    }
}

Output

node ./dist/builder/client.js
This is a Ice Igloo with 1 door(s) and 0 window(s).
This is a Sandstone Castle with 100 door(s) and 200 window(s).
This is a Wood House Boat with 6 door(s) and 8 window(s).

Summary

  • The Builder pattern is a creational pattern that is used to create more complex objects than you'd expect from a factory.
  • The Builder pattern should be able to construct complex objects in any order and include/exclude whichever available components it likes.
  • For different combinations of products than can be returned from a Builder, use a specific Director to create the bespoke combination.
  • You can use an Abstract Factory to add an abstraction between the client and Director.