Daily Dose of Swift

Builder Pattern

Photo by Monica Sauro on Unsplash

The Builder is a creation pattern that encapsulates the logic of creating a complex object and defines independent steps for its construction. The pattern has the object scope achieving its goals through composition at run time. A builder can create objects with different representations by implementing different steps as needed. Instead of passing all the necessary parameters for the construction of a complex object by the init, the builder allows to pass each parameter through a specific method.

The use of this pattern in complex objects eliminates the need to pass optional parameters, reducing the implementation of conditionals scattered throughout the code. The builders are leaner and expose less details of the object.

The Builder builds the objects using the declarative programming paradigm, which is slightly different from the imperative programming that we are used to in Swift. Declarative programming is a trend in the development community and Apple is betting on it with SwiftUI. We can find the implementation of the pattern builder in SwiftUI with ViewsModifiers. The declarative programming paradigm aims to achieve a specific state and does not focus on the details of its creation. With the builder we can create immutable objects, which receive pure inputs and provide pure outputs, thus being reliable and easy to test.

Use case

An application has screens with different NavigationBar settings. Some NavigationBars have a title with buttons on the right, others without a title with a button on the left and others have no buttons. We can build this NavigationBar in a reusable and simple way using the Builder pattern.

We created a method for each configuration that the NavigationBar needs to have but we only use what is necessary for each representation.

Implementation

class NavigationBarBuilder {
    private var navigationItem = UINavigationItem()
            
    func rightBarButton(_ barButtons:[UIBarButtonItem]) -> NavigationBarBuilder {
        navigationItem.setRightBarButtonItems(barButtons, animated: true)

        return self
    }
    
    func leftBarButton(_ barButtons:[UIBarButtonItem]) -> NavigationBarBuilder {
        navigationItem.setLeftBarButtonItems(barButtons, animated: true)

        return self
    }

    func title(_ title: String) -> NavigationBarBuilder {
        navigationItem.title = title

        return self
    }

    func build() -> UINavigationBar {
        let navigationBar = UINavigationBar()
        navigationBar.setItems([navigationItem], animated: false)
        
        return navigationBar
    }
}

let navigationWithTitleAndRightBarButton = NavigationBarBuilder()
    .title("Title")
    .rightBarButton([.init(systemItem: .add)])
    .build()

let navigationWithLeftBarButton = NavigationBarBuilder()
    .leftBarButton([.init(systemItem: .cancel)])
    .build()

let navigationWithTitle = NavigationBarBuilder()
    .title("Title")
    .build()

Advantages

  • Improves code testability
  • Helps to decrease the number of parameters of a constructor
  • Creates different representations of an object
  • Encapsulates the internal details of an object
  • Avoid passing optional parameters
  • Decrease in conditionals
  • Improves code readability and expressiveness
  • Code reuse

PS: This is just one of the many ways to implement and use the pattern builder on iOS. We can use it to build Alerts, AttributedString, ImageViews, ViewModels, Us and more. Another nice way to implement it is with closures.

Conclusion

The builder is a simple, powerful and very effective standard in what they are proposed. Its implementation can help software projects to have cleaner and testable code.

Thanks for reading!