Creational Design Patterns — iOS
Summary of @ Design Pattern by tutorial
Topics I covered:
- What are Design Patterns?
- Singleton Pattern — Creational
- Builder Pattern — Creational
- Prototype Pattern — Creational
- Factory Pattern — Creational
What are Design Patterns?
In software engineering, a design pattern is a general, repeatable solution to a commonly occurring problem in software design.
Types of design patterns
There are three main types of design patterns:
- Structural design pattern: Describes how objects are composed and combined to form larger structures. Examples of structural design patterns include Model-View-Controller (MVC), Model-View-ViewModel (MVVM), and Facade.
- Behavioral design pattern: Describes how objects communicate with each other. Examples of behavioral design patterns are Delegation, Strategy, and Observer.
- Creational design pattern: Describes how to create or instantiate objects. Examples of creational patterns are Builder, Singleton, and Prototype.
Singleton Pattern
The singleton pattern restricts a class to only one instance.
When should you use it?
Use the singleton pattern when having more than one instance of a class would cause problems, or when it just wouldn’t be logical.
Builder Pattern
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. The builder abstracts how these inputs are used to create the product, and it accepts them in whatever order the director wants to provide them.
Playground example
// MARK: - Product
public struct Hamburger {
public let meat: Meat
public let sauce: Sauces
public let toppings: Toppings
}
// MARK: - Builder
public class HamburgerBuilder {
public private(set) var meat: Meat = .beef
public private(set) var sauces: Sauces = []
public private(set) var toppings: Toppings = []
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) {
self.meat = meat
}
public func build() -> Hamburger {
return Hamburger(meat: meat,
sauce: sauces,
toppings: toppings)
}
}
// MARK: - Director
public class Employee {
public func createCombo1() throws -> Hamburger {
let builder = HamburgerBuilder()
try builder.setMeat(.beef)
builder.addSauces(.secret)
builder.addToppings([.lettuce, .tomatoes, .pickles])
return builder.build()
}
public func createKittenSpecial() throws -> Hamburger {
let builder = HamburgerBuilder()
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.
Prototype Pattern
The prototype pattern is a creational pattern that allows an object to copy itself. It involves two types:
- A copying protocol that declares copy methods.
- A prototype class that conforms to the copying protocol.
There are actually two different types of copies: shallow and deep.
A shallow copy creates a new object instance, but doesn’t copy its properties. Any properties that are reference types still point to the same original objects. For example, whenever you copy a Swift Array, which is a struct and thereby happens automatically on assignment, a new array instance is created, but its elements aren’t duplicated.
A deep copy creates a new object instance and duplicates each property as well. For example, if you deeply copy an Array, each of its elements is copied too. Swift doesn’t provide a deep copy method on an Array by default.
When should you use it?
Use this pattern to enable an object to copy itself.
For example, Foundation defines the NSCopying protocol. However, this protocol was designed for Objective-C, and unfortunately, it doesn’t work that well in Swift. You can still use it, but you’ll wind up writing more boilerplate code yourself.
Factory Pattern
The factory pattern is a creational pattern that provides a way to make objects without exposing creation logic. It involves two types:
- The factory creates objects.
- The products are the objects that are created.
When should you use it?
Use the factory pattern whenever you want to separate out product creation logic, instead of having consumers create products directly.
What should you be careful about?
Not all polymorphic objects require a factory. If your objects are very simple, you can always put the creation logic directly in the consumer, such as a view controller itself.
Alternatively, if your object requires a series of steps to build it, you may be better off using the builder pattern or another pattern instead.
Key points
You learned about the factory pattern in this chapter. Here are its key points:
- A factory’s goal is to isolate object creation logic within its own construct.
- A factory is most useful if you have a group of related products, or if you cannot create an object until more information is supplied (such as completing a network call or waiting on user input).
- The factory method adds a layer of abstraction to create objects, which reduces duplicate code.