Home Articles

Builder Design Pattern

Let us discuss how Builder design pattern in this article:

Builder Design Pattern

Intent

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. This Pattern provides a series of methods that provides transparency to the consumer of class to better understand what is happening under the hood.

The builder pattern keeps the logic and default configuration values required to create an object into a builder class. This allows consumer to create objects with minimal configuration data and without needing to know the default values that will be used to create the object.

Builder Design Pattern

What are the benefits?

  • This pattern makes it easier to change the default configuration values used to create an object and to change the class from which instances are created.

When should you use this pattern?

  • Use this pattern when a complex configuration process is required to create an object and you don’t want the default configuration values to be disseminated throughout the application.
  • This pattern is useful when you have lots of optional parameters
  • When you have to modify values later at any point of time

When should you avoid this pattern?

  • Don’t use this pattern when every data value required to create an object will be different for each instance.

Pattern correct implementation check

Consumer can create objects by providing just the data values for which there are no default values.

Any related patterns

This pattern can be combined with the factory method or abstract factory patterns

Structure

Design Pattern Structure

The builder pattern solves the problem by introducing an intermediary called the builder between a component and the object it needs to work with.

In the first operation, the consumer provides the builder with an item of data that replaces one of the default values [if present] used to create an object. The operation is repeated each time the consumer obtains a new value from the process it is following.

In the second operation, the consumer asks the builder to create an object. This signals to the builder that there will be no new data and that an object should be created using the data values it has received so far, along with default values for the data items that were not specified by the consumer.

In the third operation, the builder creates an object and returns it to the consumer with an object.

It might look complicated but it’s simple. We can compare it to LEGO, To build a square, we need four elements of same size. We can’t say to build until these 4 elements are provided.

Design Pattern Example

To understand Builder Pattern better, I tried out many approaches, You can adopt anyone which looks better to you.

I have a class with a lot of optional values. While creating instance of particular type, it just require few values. In swift it could be easily achieved with enum with associated values, but sometimes it complicate things and a lot of duplication if there is common field present.

1st Iteration:


    class OptionDetailData: Equatable {
        var isExpired: Bool = false
        var expiryDate: String?
        var cardNumber: String?
        var formattedNumber: String?
        var phoneNumber: String?
        
        static func == (lhs: OptionDetailData, rhs: OptionDetailData) -> Bool {
            return true
        }
      
      class Builder {
        
        private var detailData: OptionDetailData = OptionDetailData()
        
        func set(isExpired: Bool) -> Builder {
            detailData.isExpired = isExpired
            return self
        }
        
        func set(expiryDate: String) -> Builder {
            detailData.expiryDate = expiryDate
            return self
        }
        
        func set(cardNumber: String) -> Builder {
            detailData.cardNumber = cardNumber
            return self
        }
        
        func set(formattedNumber: String) -> Builder {
            detailData.formattedNumber = formattedNumber
            return self
        }
        
        func set(phoneNumber: String) -> Builder {
            detailData.phoneNumber = phoneNumber
            return self
        }
        
        func build() -> OptionDetailData {
            return detailData
        }
    }
    }

    let data = OptionDetailData.Builder()
        .set(isExpired: false)
        .set(expiryDate: "Today")
        .set(formattedNumber: "****1234****")
        .set(phoneNumber: "9876543210")
        .set(cardNumber: "441122224411")
        .build()
    print(data)

    let walletMetadata = OptionDetailData.Builder()
        .set(phoneNumber: "9876543210")
        .build()
    print(walletMetadata.phoneNumber)   
    

As I have lot of optionals in this class which is not required by all options. I just set required field values using builder. This one is first iteration but frankly I didn’t like the approach. As I need to create class to create model.

2nd Iteration:


    struct OptionDetailDataStruct: Equatable {
        let isExpired: Bool
        let expiryDate: String?
        let cardNumber: String?
        let formattedNumber: String?
        let phoneNumber: String?
    }

    class OptionDetailDataBuilderStruct {
        private var isExpired: Bool = false
        private var expiryDate: String?
        private var cardNumber: String?
        private var formattedNumber: String?
        private var phoneNumber: String?
        
        func set(isExpired: Bool) -> OptionDetailDataBuilderStruct {
            self.isExpired = isExpired
            return self
        }
        
        func set(expiryDate: String) -> OptionDetailDataBuilderStruct {
            self.expiryDate = expiryDate
            return self
        }
        
        func set(cardNumber: String) -> OptionDetailDataBuilderStruct {
            self.cardNumber = cardNumber
            return self
        }
        
        func set(formattedNumber: String) -> OptionDetailDataBuilderStruct {
            self.formattedNumber = formattedNumber
            return self
        }
        
        func set(phoneNumber: String) -> OptionDetailDataBuilderStruct {
            self.phoneNumber = phoneNumber
            return self
        }
        
        func build() -> OptionDetailDataStruct {
            return OptionDetailDataStruct(isExpired: isExpired, expiryDate: expiryDate, cardNumber: cardNumber, formattedNumber: formattedNumber, phoneNumber: phoneNumber)
        }
    }


    let data1 = OptionDetailDataBuilderStruct()
        .set(isExpired: false)
        .set(expiryDate: "Today")
        .set(formattedNumber: "****1234****")
        .set(phoneNumber: "9876543210")
        .set(cardNumber: "441122224411")
        .build()

    let walletMetadata1 = OptionDetailDataBuilderStruct()
        .set(phoneNumber: "9876543210")
        .build()

    print(walletMetadata.phoneNumber)        

In this I have created model using struct and builder using class so there is no mutability further on struct.

Builder pattern is also useful when you have complex logic for object creation.


    protocol ScopeFunc {}
    extension ScopeFunc {
        @inline(__always) func apply(block: (Self) -> ()) -> Self {
            block(self)
            return self
        }
    }

    class URLBuilder: ScopeFunc {
        private var components = URLComponents()

        func set(scheme: String) -> URLBuilder {
            self.components.scheme = scheme
            return self
        }

        func set(host: String) -> URLBuilder {
            apply { $0.components.host = host }
        }

        func set(port: Int) -> URLBuilder {
            apply { $0.components.port = port }
        }

        func set(path: String) -> URLBuilder {
            var path = path
            if !path.hasPrefix("/") {
                path = "/" + path
            }
            self.components.path = path
            return self
        }

        func addQueryItem(name: String, value: String) -> URLBuilder  {
            if self.components.queryItems == nil {
                self.components.queryItems = []
            }
            self.components.queryItems?.append(URLQueryItem(name: name, value: value))
            return self
        }
     
        func build() -> URL? {
            return self.components.url
        }
    }

    let url = URLBuilder()
        .set(scheme: "https")
        .set(host: "localhost")
        .set(path: "api/v1")
        .addQueryItem(name: "sort", value: "name")
        .addQueryItem(name: "order", value: "asc")
        .build()

    print(url)

You would have noticed in builder pattern, each set(#) function returns Builder itself which is duplicated line of code in each function. I was recently reading about Kotlin apply and also function which looks good candidate for functional programming. It’s difficult to create kotlin apply function in swift as it’s not supported by language. also is possible which I implemented having name apply as also doesn’t make sense here.

Other examples of builder pattern are PizzaBuilder, BurgerBuilder, URLComponents and DateComponents.

Block based builders

A more swifty approach can be the use of blocks instead of builder classes to configure objects.


    extension UILabel {
        static func build(block: ((UILabel) -> Void)) -> UILabel {
            let label = UILabel(frame: .zero)
            label.translatesAutoresizingMaskIntoConstraints = false
            block(label)
            return label
        }
    }


    var label: UILabel {
        return UILabel.build { label in
            label.text = "Hello wold!"
            label.font = UIFont.systemFont(ofSize: 12)
        }
    }

Ofcourse, you can create generic implementation of this build function if you are really incorporating to use it for all ui views and subclasses.

Here goes UIView builder for generic implementation:


    extension UIView {
         @inline(__always) static func build<T>(applyAttributes: ((T) -> Void)? = nil) -> T where T: UIView {
            let uiComponent = T(frame: .zero)
            uiComponent.translatesAutoresizingMaskIntoConstraints = false
            applyAttributes?(uiComponent)
            return uiComponent
        }
    }

    let label: UILabel = {
        return UILabel.build { label in
            label.text = "Hello wold!"
            label.font = UIFont.systemFont(ofSize: 12)
        }
    }

    let view: UIView = {
        return UIView.build()
    }

    let button: UIButton = {
        return UIButton.build {
            $0.setTitle("Button", for: .normal)
        }
    }

Please note that the builder implementation can vary on the specific use case. Sometimes a builder is combined with factories. As far as I can see almost everyone interpreted it in a different way, but I don’t think that’s a problem.

This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.