top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

Builder Pattern: Protocol Oriented Design Pattern

Updated: Aug 25, 2022


The builder pattern is a creational design pattern that separates the creation of a complex type from the representation. This enables us to have better control over the creation process while minimizing the amount of code needed, within our code base, to create the types.


Problem We Are Trying To Solve

In our application we have complex types that require initialization of numerous fields or has many steps, with other nested objects, required for initialization.


Our Solution

The solution that we will be presenting is to create a number of “builder” types that will initialize the complex type with defaults depending on which “builder” type is selected. This creates standard types within the application that we can use to create instances of the complex type ensuring we are creating them consistently.


Another option would be to use a solution similar to the solution we presented in the Proxy Pattern here where we create another type whose purpose is to handle the creation of our complex type.


The Example

There are several ways that we can separate the creation of a complex type from the representation and in this post we will look at one that I have used numerous times. This method is used when we have a type which has numerous options that needs to be configured upon creation. The following illustration shows how our example will work.



For our example we have a Burger type which contains a list of ingredients and condiments that may be on the burger and when the Burger type is created, we need to define what is on the burger. Our Burger type looks like this.


struct Burger {
    var name: String
    var patties: Int
    var bacon: Bool
    var cheese: Bool
    var ketchup: Bool
    var mustard: Bool
    var mayo: Bool
    var secretSauce: Bool
    var lettuce: Bool
    var tomato: Bool
    var pickle: Bool
    var onion: Bool
}

When we want to create an instance of the Burger type, we will need to set each property therefore code like this is required.


let hardToBuildBurger = Burger(name: "Hard to build burger", patties: 1, bacon: false, cheese: false, ketchup: true, mustard: true, mayo: false, secretSauce: false, lettuce: true, tomato: true, pickle: true, onion: true)

As we can see, this code is very hard to read and can be pretty error prone. We do have options like setting default values for each property or using optionals but each of these solutions have their own drawbacks. For example if we set default values for everything and then customized the instance afterwards we would have code like this.


let hardToBuildBurger = Burger()
hardToBuildBurger.name = “Hard to build burger”
hardToBuildBurger.cheese = false
hardToBuildBurger.ketchup = true
…

As we can see, this code can also be hard to read and error prone. The other option, of using optionals, would mean that we would have to unwrap each property when we checked it. This would also add unnecessary complexity to our code.


There is another issue with using these two options, most restaurants have a number of burgers on their menu that come with certain ingredients and condiments. Using any of the methods above relies on the developer, when creating an instance of the burger type, to make sure they have the correct ingredient for the burger they are creating. Now lets look at a better way to create our Burger type by creating burger builders. Let’s start off by defining a BurgerBuilder protocol that all burger builders will adopt.


 protocol BurgerBuilder {
    var name: String { get }
    var patties: Int { get }
    var bacon: Bool { get }
    var cheese: Bool { get }
    var ketchup: Bool { get }
    var mustard: Bool { get }
    var mayo: Bool { get }
    var secretSauce: Bool { get }
    var lettuce: Bool { get }
    var tomato: Bool { get }
    var pickle: Bool { get }
    var onion: Bool { get }
}

Notice that our BurgerBuilder protocol contains all of the same properties as our Burger type and these properties are defined as constants. Now let’s create an initiator for our Burger type that will use instances of the types that adopt the this protocol to initiate itself. The initiator has the following code


init(_ builder: BurgerBuilder) {
        self.name = builder.name
        self.patties = builder.patties
        self.bacon = builder.bacon
        self.cheese = builder.cheese
        self.ketchup = builder.ketchup
        self.mustard = builder.mustard
        self.mayo = builder.mayo
        self.secretSauce = builder.secretSauce
        self.lettuce = builder.lettuce
        self.tomato = builder.tomato
        self.pickle = builder.pickle
        self.onion = builder.onion
    } 

In the initiator we set each property using what is defined by the instance of the type that adopts the BurgerBuilder protocol. Now lets define a couple types that adopt the BurgerBuilder protocol.


struct HamburgerBuilder: BurgerBuilder {
    let name = "Hamburger"
    let patties = 1
    let bacon = false
    let cheese = false
    let ketchup = true
    let mustard = true
    let mayo = false
    let secretSauce = false
    let lettuce = true
    let tomato = true
    let pickle = true
    let onion = true
}

struct BaconDoubleCheeseBurgerBuilder: BurgerBuilder {
    let name = "Bacon Double Cheese Burger"
    let patties = 2
    let bacon = true
    let cheese = true
    let ketchup = true
    let mustard = true
    let mayo = false
    let secretSauce = false
    let lettuce = true
    let tomato = true
    let pickle = true
    let onion = true
}

Now that we have defined a couple types that adopt the BurgerBuilder protocol, lets see how we would use them to create instance of the Burger type.


let easyToBuildBurger = Burger(BaconDoubleCheeseBurgerBuilder())

As we can see the creation of this burger is much less error prone, less complex and easier to understand. Once a burger is created, if there are any changes that need to be made, like if a customer wants us to hold the tomato’s, we can then easily make the change like this.


easyToBuildBurger.tomato = false

The builder pattern allows us to separates the creation of a complex type from the representation. I look to use this pattern anytime where the creation of a type becomes very complex or if I am repeatedly using the same code in multiple parts of the code base to create an type with the same options.



masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt

bottom of page