Builder – Design Patterns in Swift

Introduction

Unlike other creational patterns, the Builder design pattern doesn’t require products to have a common interface. That makes it possible to produce different products using the same construction process.

Builder Pattern – UML

Use Builder in Swift

The Builder interface specifies methods for creating the different parts of the Product objects.

import XCTest

protocol Builder {

    func producePartA()
    func producePartB()
    func producePartC()
}

The Concrete Builder classes follow the Builder interface and provide specific implementations of the building steps. Your program may have several variations of Builders, implemented differently.

Concrete Builders are supposed to provide their own methods for retrieving results. That’s because various types of builders may create entirely different products that don’t follow the same interface. Therefore, such methods cannot be declared in the base Builder interface (at least in a statically typed programming language).

Usually, after returning the end result to the client, a builder instance is expected to be ready to start producing another product. That’s why it’s a usual practice to call the reset method at the end of the getProduct method body. However, this behavior is not mandatory, and you can make your builders wait for an explicit reset call from the client code before disposing of the previous result.

Concrete Builders

A fresh builder instance should contain a blank product object, which is used in further assembly. And all production steps work with the same product instance.

class ConcreteBuilder1: Builder {

    private var product = Product1()

    func reset() {
        product = Product1()
    }

    func producePartA() {
        product.add(part: "PartA1")
    }

    func producePartB() {
        product.add(part: "PartB1")
    }

    func producePartC() {
        product.add(part: "PartC1")
    }

    func retrieveProduct() -> Product1 {
        let result = self.product
        reset()
        return result
    }
}

Director

The Director is only responsible for executing the building steps in a particular sequence. It is helpful when producing products according to a specific order or configuration. Strictly speaking, the Director class is optional, since the client can control builders directly.

The Director works with any builder instance that the client code passes to it. This way, the client code may alter the final type of the newly assembled product. The Director can construct several product variations using the same building steps.

class Director {

    private var builder: Builder?

    func update(builder: Builder) {
        self.builder = builder
    }

    func buildMinimalViableProduct() {
        builder?.producePartA()
    }

    func buildFullFeaturedProduct() {
        builder?.producePartA()
        builder?.producePartB()
        builder?.producePartC()
    }
}

It makes sense to use this pattern only when your products are quite complex and require extensive configuration. Unlike in other creational patterns, different concrete builders can produce unrelated products. In other words, the results of various builders may not always follow the same interface.

class Product1 {

    private var parts = [String]()

    func add(part: String) {
        self.parts.append(part)
    }

    func listParts() -> String {
        return "Product parts: " + parts.joined(separator: ", ") + "\n"
    }
}

Client Code

The client code creates a builder object, passes it to the director, and then initiates the construction process. Finally, the end result is retrieved from it.

class Client {

    static func someClientCode(director: Director) {
        let builder = ConcreteBuilder1()
        director.update(builder: builder)
        
        print("Standard basic product:")
        director.buildMinimalViableProduct()
        print(builder.retrieveProduct().listParts())

        print("Standard full featured product:")
        director.buildFullFeaturedProduct()
        print(builder.retrieveProduct().listParts())

        print("Custom product:")
        builder.producePartA()
        builder.producePartC()
        print(builder.retrieveProduct().listParts())
    }
}

Let’s see how it all comes together.

class BuilderConceptual: XCTestCase {

    func testBuilderConceptual() {
        let director = Director()
        Client.someClientCode(director: director)
    }
}

Conclusion

In summary, the builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. And it intends to separate the construction of a complex object from its representation.

Leave a Reply

Your email address will not be published. Required fields are marked *