Chapter 9: Builder Pattern
大綱
When should you use it?
The builder pattern allows you to create complex objects by providing inputs step-by-step, instead of requiring all inputs upfront via an initializer. This pattern involves three main types:
The director: accepts inputs and coordinates with the builder. This is usually a view controller or a helper class that’s used by a view controller.
The product: is the complex object to be created.
This can be either a struct or a class, depending on desired reference semantics. It’s usually a model, but it can be any type depending on your use case.
The builder: accepts step-by-step inputs and handles the creation of the product.
This is often a class, so it can be reused by reference.”

When should you use it?
Use the builder pattern when you want to create a complex object using a series of steps.
This pattern works especially well when a product requires multiple inputs.
Playground example
目標: hamburger builder
The product could be a hamburger model, which has inputs such as meat selection, toppings and sauces.
The director could be an employee object, which knows how to build hamburgers, or it could be a view controller that accepts inputs from the user.
// MARK: - Example
let burgerFlipper = Employee()
if let combo1 = try? burgerFlipper.createCombo1() {
print("Nom nom " + combo1.description)
}
// Nom nom beef burger
if let kittenBurger = try?
burgerFlipper.createKittenSpecial() {
print("Nom nom nom " + kittenBurger.description)
} else {
print("Sorry, no kitten burgers here... :[")
}
// request a kitten-special burger. Since kitten is sold out, you’ll see this printed to the console:
// Sorry, no kitten burgers here... :[
Product
// MARK: - Product
public struct Hamburger {
// Once a hamburger is made, you aren’t allowed to change its components, which you codify via let properties
public let meat: Meat
public let sauce: Sauces
public let toppings: Toppings
}
extension Hamburger: CustomStringConvertible {
public var description: String {
return meat.rawValue + " burger"
}
}
// Each hamburger must have exactly one meat selection
public enum Meat: String {
case beef
case chicken
case kitten
case tofu
}
// This will allow you to combine multiple sauces together
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
}
}
// This will allow you to combine multiple Toppings together
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
}
}
Builder
public class HamburgerBuilder {
public enum Error: Swift.Error {
case soldOut
}
// Unlike a Hamburger, you declare these using var to be able to change them. You also specify private(set) for each to ensure only HamburgerBuilder can set them directly.
public private(set) var meat: Meat = .beef
public private(set) var sauces: Sauces = []
public private(set) var toppings: Toppings = []
private var soldOutMeats: [Meat] = [.kitten]
// Since you declared each property using private(set), you need to provide public methods to change them.
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)
}
// If a meat is sold out, you’ll throw an error whenever setMeat(_:) is called.
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)
}
// build() to create the Hamburger from the selections.
public func build() -> Hamburger {
return Hamburger(meat: meat,
sauce: sauces,
toppings: toppings)
}
}
Director
public class Employee {
public func createCombo1() throws -> Hamburger {
let builder = HamburgerBuilder()
// build流程
try builder.setMeat(.beef)
builder.addSauces(.secret)
builder.addToppings([.lettuce, .tomatoes, .pickles])
return builder.build()
}
public func createKittenSpecial() throws -> Hamburger {
let builder = HamburgerBuilder()
// build流程
try builder.setMeat(.kitten)
builder.addSauces(.mustard)
builder.addToppings([.lettuce, .tomatoes])
return builder.build()
}
}
What should you be careful about?
The builder pattern works best for creating complex products that require multiple inputs using a series of steps.
If your product doesn’t have several inputs or can’t be created step by step, the builder pattern may be more trouble than it’s worth.
Instead, consider providing convenience initializers to create the product.
Tutorial project
Key points
The builder pattern is great for creating complex objects in a step-by-step fashion. It involves three objects: the director, product and builder.
The director accepts inputs and coordinates with the builder; the product is the complex object that’s created; and the builder takes step-by-step inputs and creates the product.
Last updated
Was this helpful?