Throughout my career I’ve learned some design patterns that make my code so much cleaner.
Here are my top 12 design patterns that you should know:
In this article I’ll explain each of these patterns, when you should use them and when you shouldn’t.
A singleton class only allows one instance of itself to be created.
This is some-what disputed design pattern, as some critics say it’s an anti-pattern as it introduces global state into your application.
There are definitely right and wrong times to use a singleton pattern, and you should only use it when you are sure you only want one instance of a class created.
Apple uses the Singleton pattern often in their code.
Audio channels to play sound effects and network managers are good examples where singletons can be useful.
To create singleton use a static type property. Static properties are guaranteed to be lazily initialized only once, even across multiple threads.
class SomeSingleton {
static let sharedInstace = SomeSingleton()
}
If you need to do some additional configuration you can use the following code:
class SomeSingleton {
static let sharedInstance: SomeSingleton = {
let singleton = SomeSingleton()
// other configuration code here
return singleton
}
}
Try to avoid using singletons for global state, as they can quickly balloon in size and become unmanageable.
A facade is a a structural design pattern. The word facade means the “face of a building”.
As the name suggests, the clients walking outside don’t know what’s inside the building. The complexities of the electrical, plumbing, elevators and stairways…
The facade design pattern does the same, it hides the complexities of a system and displays a friendly face.
This allows clients to query more complex systems with a simplified interface.
A good example of this facade is the start up of a computer.
When you turn on your computer of bunch of different things happen.
For the user or client, this would be annoying to do all these tasks every time.
So the power button acts as a facade, the user just pushes a button and all the necessary steps to power on the computer are activated.
The facade design pattern is great to use when you want to simply the interface of a complex system.
You should not use a facade if the system is already simple, as this adds an unnecessary layer of code.
The factory design pattern is a very popular pattern not only in Swift, but in many programming languages.
The factory pattern allows you to create an object without exposing the creation details to the client. The object adheres to a common interface or in Swift, a common protocol.
Here is the shape example in Swift:
import UIKit
protocol Shape {
func draw()
}
struct Triangle: Shape {
func draw() {
print("Drawing a Triangle!")
}
}
struct Circle: Shape {
func draw() {
print("Drawing a Circle!")
}
}
struct Rectangle: Shape {
func draw() {
print("Drawing a Rectangle!")
}
}
struct ShapeFactory {
func getShape(shapeType: String) -> Shape? {
if shapeType == "circle" {
return Circle()
} else if shapeType == "rectangle" {
return Rectangle()
} else if shapeType == "triangle" {
return Triangle()
}
return nil
}
}
let userInput = "triangle"
let shapeFactory = ShapeFactory()
let shape = shapeFactory.getShape(shapeType: userInput)
shape?.draw() // prints: Drawing a Triangle!
So when is the best time to used a factory pattern?
When you don’t know what kind of object until runtime.
In the above example, we’re not sure whether we need a rectangle, shape or circle until some user input.
If we didn’t use the factory pattern, we would have to create a bunch of if statements on the user input like this:
let userInput = "triangle"
var shape: Shape?
if userInput == "circle" {
shape = Circle()
} else if userInput == "rectangle" {
shape = Rectangle()
} else if userInput == "triangle" {
shape = Triangle()
}
shape?.draw()
The factory method just keeps all this organized in one struct.
When should you not use factories? Well, most of the time…
Only use factories when you need to abstract away object creation.
You should ask yourself, does this make your code better? Easier to read?
Decorators are a conceptual design pattern that allows you to add new functionality to an object without altering its original structure.
They are quite often used in streams. Decorators are essentially wrapper objects.
You can wrap objects as many times as you want, since both the object and the decorator follow the same interface.
Here’s an example of how to use it.
protocol Shape {
func draw() -> String
}
class Triangle: Shape {
func draw() -> String {
return "drawing triangle"
}
}
class Decorator: Shape {
private var shape: Shape
init(_ shape: Shape) {
self.shape = shape
}
func draw() -> String {
return shape.draw()
}
}
class BorderDecorator: Decorator {
override func draw() -> String {
return "\(super.draw()) + drawing border"
}
}
class FillDecorator: Decorator {
override func draw() -> String {
return "\(super.draw()) + filling in shape with blue color"
}
}
let triangle = Triangle()
print(triangle.draw())
let triangleWithBorder = BorderDecorator(triangle)
print(triangleWithBorder.draw())
let triangleWithBorderAndFilled = FillDecorator(triangleWithBorder)
print(triangleWithBorderAndFilled.draw())
As you can see, you can wrap an object in as many decorators as you want.
Be sure when you’re creating decorators that you consider the pros and cons of just modifying the original struct.
Memento is a behavioral design pattern that captures the current state of an object and allows it to be restored in the future.
Kind of the like the movie… Memento. A great flick by the way if you haven’t seen it yet.
This is great when you need to restore a state, for example if your app allows the user to “Undo” certain actions.
The memento design pattern consists of thee objects: the originator, a caretaker and a memento.
The originator is an object that has an internal state.
The caretaker is the one that changes the internal state of the originator, but wants to be able to undo the change.
The memento is what tracks all the changes.
Here’s an example of the memento design pattern in Swift:
class Originator {
private var state: String
init(state: String) {
self.state = state
print("Originator state: \(state)")
}
func changeState() {
print("Originator is changing state")
state = getRandomString()
print("Originator state is now: \(state)")
}
private func getRandomString() -> String {
return String(UUID().uuidString.suffix(10))
}
func save() -> SomeMemento {
return SomeMemento(state: state)
}
func restore(memento: SomeMemento) {
self.state = memento.state
print("Originator: My state has changed to: \(state)")
}
}
protocol Memento {
var name: String { get }
var date: Date { get }
}
class SomeMemento: Memento {
var state: String
var date: Date
init(state: String) {
self.state = state
self.date = Date()
}
var name: String { return "\(state) - \(date.description)" }
}
class Caretaker {
private lazy var mementos = [SomeMemento]()
private var originator: Originator
init(originator: Originator) {
self.originator = originator
}
func backup() {
print("Caretaker: Saving state")
mementos.append(originator.save())
}
func undo() {
print("Caretaker: Undoing state")
originator.restore(memento: mementos.removeLast())
}
}
let originator = Originator(state: "Random String Here")
let caretaker = Caretaker(originator: originator)
caretaker.backup()
originator.changeState()
caretaker.backup()
originator.changeState()
caretaker.backup()
originator.changeState()
caretaker.undo()
caretaker.undo()
caretaker.undo()
This will print out:
Originator state: Random String Here
Caretaker: Saving state
Originator is changing state
Originator state is now: 757A7CED44
Caretaker: Saving state
Originator is changing state
Originator state is now: 55BE176B81
Caretaker: Saving state
Originator is changing state
Originator state is now: D95496BC60
Caretaker: Undoing state
Originator: My state has changed to: 55BE176B81
Caretaker: Undoing state
Originator: My state has changed to: 757A7CED44
Caretaker: Undoing state
Originator: My state has changed to: Random String Here
The adapter pattern is similar to an electrical outlet adapter.
When you’re traveling you use an outlet adapter to plug in your phone and other electronics.
An adapter pattern converts a class interface into another interface that is expected by the client.
This allows objects that normally couldn’t work, be able to interface with one another.
This often used to make legacy code work with the newer code.
There are three main actors in the adapter design pattern: the adaptee, the adapter and the target.
The adapter makes the adaptee’s interface compatible with the target interface.
Here’s an example of this pattern in use:
class Target {
func orderBurger() -> String {
return "I want a hamburger with onions and cheese."
}
}
class Adaptee {
public func specialOrderBurger() -> String {
return ".puhctek dna seotamot htiw regrubmah a tnaw I"
}
}
class Adapter: Target {
private var adaptee: Adaptee
init(_ adaptee: Adaptee) {
self.adaptee = adaptee
}
override func orderBurger() -> String {
return "Adapter: " + adaptee.specialOrderBurger().reversed()
}
}
print("I can understand the target's order fine:")
let target = Target()
print(target.orderBurger())
print("I cannot understand the adaptee's order:")
let adaptee = Adaptee()
print(adaptee.specialOrderBurger())
print("With a help of an adapter I can understand the adaptee's order:")
let adapter = Adapter(adaptee)
print(adapter.orderBurger())
This prints out:
I can understand the target's order fine:
I want a hamburger with onions and cheese.
I cannot understand the adaptee's order:
.puhctek dna seotamot htiw regrubmah a tnaw I
With a help of an adapter I can understand the adaptee's order:
Adapter: I want a hamburger with tomatoes and ketchup.
The observer pattern is a one-to-many relationship.
It’s best used when there’s a relationship between objects such that if one object is modified, the observing objects are notified automatically.
The object being watched is called the subject and the dependent objects are the observers.
A good example of this is an email newsletter.
The subject in this case is the email newsletter.
The observers are the subscribers.
Every time there is a new email newsletter, all the subscribers are immediately notified.
Then each subscriber can perform an action when notified. In this email example, subscribers can choose to read it, delete it, forward it or reply to it.
Here’s an example in Swift code:
class Subject {
var number: Int = 2
private lazy var observers = [Observer]()
func subscribe(_ observer: Observer) {
print("Subject: Subscribed an observer")
observers.append(observer)
}
func publish() {
print("Subject: Publishing to observers")
observers.forEach({ $0.update(subject: self)})
}
func addOne() {
number += 1
publish()
}
}
protocol Observer: class {
func update(subject: Subject)
}
class OddObserver: Observer {
func update(subject: Subject) {
if subject.number % 2 == 1 {
print("Wohoo odd number!")
}
}
}
class EvenObserver: Observer {
func update(subject: Subject) {
if subject.number % 2 == 0 {
print("Yay an even number!")
}
}
}
let subject = Subject()
let observer1 = OddObserver()
let observer2 = EvenObserver()
subject.subscribe(observer1)
subject.subscribe(observer2)
subject.addOne()
subject.addOne()
The observer pattern is best used in a one-to-many object relationship. Consider another pattern if the object relationship does not look like this .
The iterator pattern allows sequential traversal through data structures without exposing the internal workings.
For example, usually when you iterate through an array of numbers, they will come out ordered by array index.
var someNumbers = [1, 2, 3]
for number in someNumbers {
print(number)
}
Prints:
1
2
3
But what if you had a special data structure that reversed the order?
class ReverseNumbersCollection {
fileprivate lazy var items = [Int]()
func append(_ item: Int) {
self.items.append(item)
}
}
extension ReverseNumbersCollection: Sequence {
func makeIterator() -> AnyIterator<Int> {
var index = self.items.count - 1
return AnyIterator {
defer { index -= 1 }
return index >= 0 ? self.items[index] : nil
}
}
}
let someNumbers = ReverseNumbersCollection()
someNumbers.append(1)
someNumbers.append(2)
someNumbers.append(3)
for number in someNumbers {
print(number)
}
This will print:
3
2
1
Notice how the for loop
knew nothing about the internal workings of the ReverseNumbersCollection
but was able to output the correct result.
The pattern is useful when you want to create a collection of objects that need a specific traversal method.
The mediator design pattern helps reduce the coupling between two objects.
The objects communicate indirectly, through a mediator instead of directly.
This can simplify and decouple parts of the codebase.
The mediator “adds functionality” by orchestrating different actions by different objects. This is the main difference between mediators and facades.
A good example of this is air traffic controller at an airport.
Air traffic control is responsible for the planes interaction with each other. When they can land and when they can take off.
The planes do not talk to each other directly.
Here is a code example in Swift:
protocol Mediator: AnyObject {
func notify(sender: BaseWidget, event: String)
}
class SomeMediator: Mediator {
private var widget1: Widget1
private var widget2: Widget2
init(_ widget1: Widget1, _ widget2: Widget2) {
self.widget1 = widget1
self.widget2 = widget2
widget1.update(mediator: self)
widget2.update(mediator: self)
}
func notify(sender: BaseWidget, event: String) {
if event == "A" {
widget2.executeC()
}
else if (event == "D") {
widget1.executeB()
widget2.executeC()
}
}
}
class BaseWidget {
fileprivate weak var mediator: Mediator?
init(mediator: Mediator? = nil) {
self.mediator = mediator
}
func update(mediator: Mediator) {
self.mediator = mediator
}
}
class Widget1: BaseWidget {
func executeA() {
print("Widget 1 doing A!")
mediator?.notify(sender: self, event: "A")
}
func executeB() {
print("Widget 1 doing B!")
mediator?.notify(sender: self, event: "B")
}
}
class Widget2: BaseWidget {
func executeC() {
print("Widget 2 doing C!")
mediator?.notify(sender: self, event: "C")
}
func executeD() {
print("Widget 2 doing D!")
mediator?.notify(sender: self, event: "D")
}
}
let widget1 = Widget1()
let widget2 = Widget2()
let mediator = SomeMediator(widget1, widget2)
widget1.executeA()
widget2.executeD()
This pattern is useful if you don’t want your objects directly communicating with each other. You should consider the alternative of direct communication before using a mediator design pattern.
The command design pattern is that turns an action or request into an encapsulated object, which contains all the information required for the request.
For example, think about saving a document in a word processor.
There are multiple ways you could save the document, you could use the keyboard shortcut CMD + S or go to File > Save.
Now both the menu and the keyboard shortcut could save the document themselves, but this would duplicate logic.
It’s better to create an object that’s the SaveCommand
which can be used by different objects.
Now when you want to implement an auto-save feature, maybe the document saves automatically every 5 minutes, you can just use the SaveCommand
with a timer.
The template design pattern defines a skeleton of a task or algorithm in the superclass but allows subclasses to override certain parts of the algorithm.
You should use the templating design pattern when you want to control at which point subclassing/overriding is allowed.
For example, maybe you have a document parser that can read files and save it to the database. Certain parts of the reading action are the same.
Checking the created date, the modified date and the name of the file. These can all be extracted and save to the data base with the template function.
However, for different file types, say for word documents vs CSVs or PDFs, you’ll need a different method to read the content of the files.
This is where the subclasses come in to override the parsing function.
The strategy design pattern is when you define a group of algorithms, each in a separate class and then choose which one you want to use at run-time.
A good example of this is pricing strategies. Say maybe at happy hour everything is 50% off. And maybe on Tuesday’s all Wings are 50 cents each.
Perhaps Thursday seniors get a drink for free.
Each of these pricing strategies can be determined at run time and the bill can be printed correctly.
There are a couple of other design patterns such as MVVM & MVC. Those design patterns are also great to learn and deserve their own article.