The constructor pattern allows complex objects to be created step by step rather than all at once. This pattern consists of three main types, as shown in the UML diagram below:

  1. Director receives input and coordinates with Builder. Director is usually a View Controller or a helper class that the View Controller uses.
  2. A Product is a complex object that is created, which can be a class or struct, depending on whether you want to reference semantics. Usually a model.
  3. The Builder takes step by step input and controls the creation of the Product. Often a class that can be reused by reference.

When should you use it?

We can use the constructor pattern when we want to create a complex object using a series of steps. This pattern works especially well when a product requires several parts of input. How the constructor will create an input abstraction for the product and receive them in whatever order the director wants to provide the inputs. For example, we could use this pattern to implement a “hamburger constructor”. The product might be a Humburger object that has some inputs, such as meat, toppings, and pepper sauce. The director could be an Employee object that knows how to make a burger, or it could be a View Controller that accepts input from the user. The burger builder can receive meat selections, toppings and pepper sauces in any order and create a burger on demand.

Playground example

Builder is a creative mode. This is because the constructor pattern is a pattern for creating complex products. Here we will implement an example of a hamburger constructor. First, let’s create a ** product** :

// MARK: - Product
public struct Hamburger {
  public let meat: Meat
  public let sauce: Sauces
  public let toppings: Toppings
}

extension Hamburger: CustomStringConvertible {
  public var description: String {
    return meat.rawValue + " burger"}}public enum Meat: String {
  case beef
  case chicken
  case kitten
  case tofu
}

public struct Sauces: OptionSet {
  public static let mayonnaise = Sauces(rawValue: 1 << 0)
  public static let mustard = Sauces(rawValue: 1 << 1)
  public static let ketchup = Sauces(rawValue: 1 << 2)
  public static let secret = Sauces(rawValue: 1 << 3)
  
  public let rawValue: Int
  public init(rawValue: Int) {
    self.rawValue = rawValue
  }
}

public struct Toppings: OptionSet {
  public static let cheese = Toppings(rawValue: 1 << 0)
  public static let lettuce = Toppings(rawValue: 1 << 1)
  public static let pickles = Toppings(rawValue: 1 << 2)
  public static let tomatoes = Toppings(rawValue: 1 << 3)
  
  public let rawValue: Int
  public init(rawValue: Int) {
    self.rawValue = rawValue
  }
}
Copy the code

Next, we define a Builder

// MARK: - Builder
public class HamburgerBuilder {
  
  public enum Error: Swift.Error {
    case soldOut
  }
  
  public private(set) var meat: Meat = .beef
  public private(set) var sauces: Sauces = []
  public private(set) var toppings: Toppings = []
  
  private var soldOutMeats: [Meat] = [.kitten]
  
  public func addSauces(_ sauce: Sauces) {
    sauces.insert(sauce)
  }
  
  public func removeSauces(_ sauce: Sauces) {
    sauces.remove(sauce)
  }
  
  public func addToppings(_ topping: Toppings) {
    toppings.insert(topping)
  }
  
  public func removeToppings(_ topping: Toppings) {
    toppings.remove(topping)
  }
  
  public func setMeat(_ meat: Meat) throws {
    guard isAvailable(meat) else { throw Error.soldOut }
    self.meat = meat
  }
  
  public func isAvailable(_ meat: Meat) -> Bool {
    return! soldOutMeats.contains(meat)
  }
  
  public func build(a) -> Hamburger {
    return Hamburger(meat: meat,
                     sauce: sauces,
                     toppings: toppings)
  }
}
Copy the code

Next, we add director:

// MARK: - Director
public class Employee {
  
  public func createCombo1(a) throws -> Hamburger {
    let builder = HamburgerBuilder(a)try builder.setMeat(.beef)
    builder.addSauces(.secret)
    builder.addToppings([.lettuce, .tomatoes, .pickles])
    return builder.build()
  }
  
  public func createKittenSpecial(a) throws -> Hamburger {
    let builder = HamburgerBuilder(a)try builder.setMeat(.kitten)
    builder.addSauces(.mustard)
    builder.addToppings([.lettuce, .tomatoes])
    return builder.build()
  }
}
Copy the code

Here’s a test:

// MARK: - Example
let burgerFlipper = Employee(a)if let combo1 = try? burgerFlipper.createCombo1() {
  print("Nom nom " + combo1.description)
}

if let kittenBurger = try?
  burgerFlipper.createKittenSpecial() {
  print("Nom nom nom " + kittenBurger.description)
  
} else {
  print("Sorry, no kitten burgers here... : [")}Copy the code

You can see the console print:

Nom nom beef burger
Sorry, no kitten burgers here... : [Copy the code

What should you be careful about?

The constructor pattern works well for creating complex objects that require multiple inputs. If your product has few inputs or can’t be created step by step, constructors may not be suitable, and convenience constructors may be more suitable.

Tutorial project

Here we continue to add functionality to the previous app. In this section we will use the constructor pattern to add the ability to create a new QuestionGroup. The implementation effect is as follows:

Demo

trailer

In the next section we will look at learning the MVVM design pattern, which will start a new project. That’s the end of the project.