Decouples request senders from receivers by allowing multiple handlers to process a request sequentially. Each handler decides to act or pass it down the chain.
Example - Expense Approval System A Swift implementation of the Chain of Responsibility behavioral design pattern for handling multi-level expense approvals in a corporate workflow. Define Expenses -> Build the Chain -> Submit for Approval Features
VP
) without changing existing code.// MARK: - Expense Report Model
/// Represents an expense request submitted for approval.
struct ExpenseReport {
let id: UUID
let amount: Double
let description: String
let category: String // e.g., "Travel", "Equipment"
}
// MARK: - Handler Protocol
/// Defines the interface for all approval handlers in the chain.
protocol ExpenseApprover: AnyObject {
var nextApprover: ExpenseApprover? { get set }
func approve(_ expense: ExpenseReport) -> String
}
// MARK: - Concrete Handlers
/// Manager can approve expenses up to $1,000.
final class Manager: ExpenseApprover {
var nextApprover: ExpenseApprover?
private let approvalLimit: Double = 1_000
func approve(_ expense: ExpenseReport) -> String {
if expense.amount <= approvalLimit {
return "โ
Manager approved expense #\(expense.id) (\(expense.description))"
} else {
return nextApprover?.approve(expense) ?? "โ Rejected: No higher authority!"
}
}
}
/// Director can approve expenses up to $5,000.
final class Director: ExpenseApprover {
var nextApprover: ExpenseApprover?
private let approvalLimit: Double = 5_000
func approve(_ expense: ExpenseReport) -> String {
if expense.amount <= approvalLimit {
return "โ
Director approved expense #\(expense.id) (\(expense.description))"
} else {
return nextApprover?.approve(expense) ?? "โ Rejected: Requires CFO review!"
}
}
}
/// CFO is the final authority with special rules for travel expenses.
final class CFO: ExpenseApprover {
var nextApprover: ExpenseApprover? = nil
func approve(_ expense: ExpenseReport) -> String {
if expense.category == "Travel" && expense.amount > 10_000 {
return "โ Rejected: Travel expenses cannot exceed $10,000"
} else {
return "โ
CFO approved expense #\(expense.id)"
}
}
}
// MARK: - Chain Setup & Demo
func buildApprovalChain() -> ExpenseApprover {
let manager = Manager()
let director = Director()
let cfo = CFO()
manager.nextApprover = director
director.nextApprover = cfo
return manager
}
// Example Usage
let chain = buildApprovalChain()
let expenses = [
ExpenseReport(id: UUID(), amount: 800, description: "Team lunch", category: "Food"),
ExpenseReport(id: UUID(), amount: 4_500, description: "Conference tickets", category: "Travel"),
ExpenseReport(id: UUID(), amount: 12_000, description: "Business class flight", category: "Travel")
]
for expense in expenses {
print(chain.approve(expense))
}
/* Output:
โ
Manager approved expense #... (Team lunch)
โ
Director approved expense #... (Conference tickets)
โ Rejected: Travel expenses cannot exceed $10,000
*/
Example Scenario: A food delivery app (like Uber Eats) needs to handle orders, undo operations (cancel items), queue requests, and remote kitchen workflows. The Command Pattern encapsulates each order as an object, making it easy to execute, queue, or undo operations. Swift Implementation: Restaurant Order System
(Single-file, scalable, with clear comments)
Key Components
Command Protocol: Defines execute() and undo().
Concrete Commands: Encapsulate actions like AddToCartCommand, CancelOrderCommand.
Invoker (OrderManager): Manages command execution, history (for undo), and queuing.
Receiver (KitchenSystem): Actually performs the actions (e.g., cooks food).
import Foundation
// MARK: - ๐ฝ๏ธ Receiver
// The Receiver is the object that knows how to perform the actual work.
// It contains the business logic for the actions requested by the commands.
final class KitchenSystem {
private var preparedDishes: [String] = []
func prepareOrder(_ dish: String) {
preparedDishes.append(dish)
print("๐งโ๐ณ Cooking: \(dish).")
}
func cancelOrder(_ dish: String) {
if let index = preparedDishes.firstIndex(of: dish) {
preparedDishes.remove(at: index)
print("๐ซ Cancelled: \(dish).")
} else {
print("โ Error: \(dish) was not in the prepared list.")
}
}
}
// MARK: - ๐ Command Protocol
// This protocol defines the common interface for all commands.
// It requires an `execute` method to perform an action and an `undo` method to reverse it.
protocol Command {
func execute()
func undo()
}
// MARK: - โ AddToCartCommand
// This concrete command encapsulates the request to add an item to the cart.
// It holds a reference to the Receiver (`KitchenSystem`) and the necessary parameters (`dish`).
final class AddToCartCommand: Command {
private let kitchen: KitchenSystem
private let dish: String
init(kitchen: KitchenSystem, dish: String) {
self.kitchen = kitchen
self.dish = dish
}
func execute() {
print("โก๏ธ Executing AddToCartCommand.")
kitchen.prepareOrder(dish)
}
func undo() {
print("โฌ
๏ธ Undoing AddToCartCommand.")
kitchen.cancelOrder(dish)
}
}
// MARK: - โ CancelOrderCommand
// This concrete command encapsulates the request to cancel an order.
final class CancelOrderCommand: Command {
private let kitchen: KitchenSystem
private let dish: String
init(kitchen: KitchenSystem, dish: String) {
self.kitchen = kitchen
self.dish = dish
}
func execute() {
print("โก๏ธ Executing CancelOrderCommand.")
kitchen.cancelOrder(dish)
}
func undo() {
// Undoing a cancellation is complex and context-dependent.
// In this simple example, we'll log a message. In a real app,
// this might involve creating a new 'prepare' command.
print("โฌ
๏ธ Undoing CancelOrderCommand is not supported in this implementation.")
}
}
// MARK: - ๐๏ธ Invoker
// The Invoker holds and manages the commands. It doesn't know about the
// details of the commands or the Receiver. Its job is to execute and manage
// a history of commands, enabling features like undo/redo.
final class CommandInvoker {
private var history = [Command]()
func executeCommand(_ command: Command) {
command.execute()
history.append(command)
}
func undoLastCommand() {
guard !history.isEmpty else {
print("โ ๏ธ No commands to undo.")
return
}
let lastCommand = history.removeLast()
lastCommand.undo()
}
}
// MARK: - ๐ Usage
// The client code creates the commands and hands them off to the Invoker.
// It interacts with the Invoker, not the commands or the Receiver directly,
// demonstrating a clean separation of concerns.
let kitchen = KitchenSystem()
let invoker = CommandInvoker()
// User adds items to the cart, the invoker executes the commands
invoker.executeCommand(AddToCartCommand(kitchen: kitchen, dish: "Burger"))
invoker.executeCommand(AddToCartCommand(kitchen: kitchen, dish: "Pizza"))
// User decides to undo the last action
print("\nUser decides to undo the last action...\n")
invoker.undoLastCommand()
// User undoes another action
print("\nUser decides to undo another action...\n")
invoker.undoLastCommand()
This oneโs a bit rarer in day-to-day iOS code, but itโs perfect for situations where you need to define a grammar and evaluate sentences in that grammar โ like a mini language, filter system, or search query parser
Weโll make a very small interpreter that can evaluate expressions like:
"5 + 3 - 2" โ 6
import Foundation
// MARK: - Expression Protocol
protocol Expression {
func interpret() -> Int
}
// MARK: - Terminal Expression
struct NumberExpression: Expression {
private let number: Int
init(_ number: Int) {
self.number = number
}
func interpret() -> Int {
return number
}
}
// MARK: - Non-Terminal Expressions
struct AddExpression: Expression {
private let left: Expression
private let right: Expression
init(_ left: Expression, _ right: Expression) {
self.left = left
self.right = right
}
func interpret() -> Int {
return left.interpret() + right.interpret()
}
}
struct SubtractExpression: Expression {
private let left: Expression
private let right: Expression
init(_ left: Expression, _ right: Expression) {
self.left = left
self.right = right
}
func interpret() -> Int {
return left.interpret() - right.interpret()
}
}
// MARK: - Client Code: Parsing a simple space-separated expression
func parseExpression(_ input: String) -> Expression {
let tokens = input.split(separator: " ").map { String($0) }
var currentExpression: Expression = NumberExpression(Int(tokens[0])!)
var index = 1
while index < tokens.count {
let op = tokens[index]
let number = NumberExpression(Int(tokens[index + 1])!)
if op == "+" {
currentExpression = AddExpression(currentExpression, number)
} else if op == "-" {
currentExpression = SubtractExpression(currentExpression, number)
}
index += 2
}
return currentExpression
}
// MARK: - Usage
let expression = parseExpression("5 + 3 - 2")
print("Result: \(expression.interpret())") // Result: 6
"number (+ number | - number)*"
NumberExpression
AddExpression
, SubtractExpression
The Iterator is a behavioral design pattern that provides a way to access elements of a collection sequentially without exposing its underlying representation. Itโs one of the most fundamental and frequently used patterns in iOS development. Core Concept
Problem: You need to traverse different collections (arrays, trees, graphs) in a standardized way without coupling your code to their specific implementations.
Solution: The Iterator pattern:
Extracts the traversal behavior into a separate object
Provides a common interface for accessing elements
Keeps track of the current position
struct FoodItem: CustomStringConvertible {
let name: String
let isVegetarian: Bool
let price: Double
var description: String {
return name
}
}
// ---------------------------------------------------------------------------------------------------------------------
struct Menu {
private let items: [FoodItem]
init(items: [FoodItem]) {
self.items = items
}
}
extension Menu: Sequence {
// The makeIterator() method needs to return an iterator
// that knows how to filter for vegetarian items.
func makeIterator() -> VegetarianIterator {
return VegetarianIterator(items: self.items)
}
}
struct VegetarianIterator: IteratorProtocol {
private var internalIterator: IndexingIterator<[FoodItem]>
init(items: [FoodItem]) {
self.internalIterator = items.makeIterator()
}
mutating func next() -> FoodItem? {
while let item = internalIterator.next() {
if item.isVegetarian {
return item
}
}
return nil
}
}
// ---------------------------------------------------------------------------------------------------------------------
// Usage:
let menu = Menu(items: [
FoodItem(name: "Pizza", isVegetarian: true, price: 12.99),
FoodItem(name: "Steak", isVegetarian: false, price: 24.99),
FoodItem(name: "Salad", isVegetarian: true, price: 8.99)
])
print("Vegetarian options:")
for item in menu {
print("- \(item.name) at $\(item.price)")
}
// Prints
//Vegetarian options:
//- Pizza at $12.99
//- Salad at $8.99
Real-World iOS Examples
// Core Data NSFetchedResultsController:
let controller: NSFetchedResultsController<FoodItem> = ...
for item in controller.fetchedObjects ?? [] {
// Iterates through managed objects
}
// File System Enumeration:
let files = FileManager.default.enumerator(atPath: "/path")
while let file = files?.nextObject() as? String {
print(file)
}
// Combine Publishers:
[1, 2, 3]
.publisher
.sink { value in
print(value) // Implicit iterator
}
Key Benefits in iOS Development
The Mediator pattern centralizes complex communication between related objects, making them communicate through a single mediator instead of directly. This reduces tight coupling and simplifies maintenance, especially useful in UI frameworks like chat apps or Stock trading app
Example:
// Mediator Protocol
protocol AirTrafficControl {
func register(aircraft: Aircraft)
func send(message: String, from sender: Aircraft)
}
// Concrete Mediator
class ControlTower: AirTrafficControl {
private var aircrafts: [Aircraft] = []
func register(aircraft: Aircraft) {
aircrafts.append(aircraft)
aircraft.controlTower = self
}
func send(message: String, from sender: Aircraft) {
for aircraft in aircrafts {
// Don't send the message back to the sender
if aircraft !== sender {
aircraft.receive(message: message)
}
}
}
}
// Colleague Protocol
protocol Aircraft: AnyObject {
var name: String { get }
var controlTower: AirTrafficControl? { get set }
func send(message: String)
func receive(message: String)
}
// Concrete Colleagues
class Airplane: Aircraft {
let name: String
weak var controlTower: AirTrafficControl?
init(name: String) {
self.name = name
}
func send(message: String) {
print("\(name) sends: \(message)")
controlTower?.send(message: message, from: self)
}
func receive(message: String) {
print("\(name) receives: \(message)")
}
}
// Usage
let tower = ControlTower()
let boeing747 = Airplane(name: "Boeing 747")
let airbusA380 = Airplane(name: "Airbus A380")
let cessna172 = Airplane(name: "Cessna 172")
tower.register(aircraft: boeing747)
tower.register(aircraft: airbusA380)
tower.register(aircraft: cessna172)
boeing747.send(message: "Requesting permission to land")
airbusA380.send(message: "Maintaining current altitude")
cessna172.send(message: "Beginning approach to runway")
---- Output ----
Boeing 747 sends: Requesting permission to land
Airbus A380 receives: Requesting permission to land
Cessna 172 receives: Requesting permission to land
Airbus A380 sends: Maintaining current altitude
Boeing 747 receives: Maintaining current altitude
Cessna 172 receives: Maintaining current altitude
Cessna 172 sends: Beginning approach to runway
Boeing 747 receives: Beginning approach to runway
Airbus A380 receives: Beginning approach to runway
AnyObject
constraint for reference typesweak
reference to mediator to avoid retain cyclesThis pattern is particularly useful in scenarios where many objects need to communicate in complex ways, such as chat rooms, GUI components, or as shown here, air traffic control systems.
Captures and externalizes an objectโs state for later restoration without breaking encapsulation. Enables undo/redo functionality and snapshots.
Example: Text editor undo stack, game save points, or database transactions.
iOS System Examples
let undoManager = UndoManager()
undoManager.registerUndo(withTarget: self) {
$0.restore(from: memento)
}
// Core Data's Rollback
context.rollback() // Reverts to last persisted state
Letโs understand with an example - Memento Pattern: State Snapshot System
Overview Captures and externalizes an objectโs state for later restoration while maintaining encapsulation.
Key Components
Originator
- The object that produces state snapshotsMemento
- Immutable state containerCaretaker
- Manages memento historyimport Foundation
// MARK: - Originator (Text Editor)
final class TextEditor {
private var text: String
private var cursorPosition: Int
init(text: String = "") {
self.text = text
self.cursorPosition = text.count
}
func type(_ char: Character) {
text.insert(char, at: text.index(text.startIndex, offsetBy: cursorPosition))
cursorPosition += 1
printState()
}
func delete() {
guard cursorPosition > 0 else { return }
text.remove(at: text.index(text.startIndex, offsetBy: cursorPosition-1))
cursorPosition -= 1
printState()
}
private func printState() {
print("๐ [\(cursorPosition)] \(text)")
}
// MARK: Memento Creation
func save() -> EditorMemento {
return EditorMemento(
text: text,
cursor: cursorPosition,
date: Date()
)
}
func restore(from memento: EditorMemento) {
self.text = memento.text
self.cursorPosition = memento.cursorPosition
print("โ Restored to \(memento.date.formatted()):")
printState()
}
}
// MARK: - Memento (State Snapshot)
struct EditorMemento {
let text: String
let cursorPosition: Int
let date: Date
fileprivate init(text: String, cursor: Int, date: Date) {
self.text = text
self.cursorPosition = cursor
self.date = date
}
}
// MARK: - Caretaker (Undo Manager)
final class UndoHistory {
private var stack: [EditorMemento] = []
func save(_ memento: EditorMemento) {
stack.append(memento)
print("๐พ Saved state (\(stack.count) total)")
}
func undo() -> EditorMemento? {
guard stack.count > 1 else { return nil }
_ = stack.popLast()
return stack.last
}
func clear() {
stack.removeAll()
}
}
// MARK: - Usage Example
func demonstrateTextEditor() {
let editor = TextEditor()
let history = UndoHistory()
// Initial save
history.save(editor.save())
// Edit document
"Hello".forEach { editor.type($0) }
history.save(editor.save())
editor.type(" ")
editor.type("W")
editor.type("o")
editor.delete() // Deletes 'o'
editor.type("r")
history.save(editor.save())
// Undo last 2 actions
print("\n--- Undo ---")
if let memento = history.undo() {
editor.restore(from: memento)
}
// Undo again
if let memento = history.undo() {
editor.restore(from: memento)
}
}
// Run the demo
demonstrateTextEditor()
/* Expected Output:
๐ [1] H
๐ [2] He
๐ [3] Hel
๐ [4] Hell
๐ [5] Hello
๐พ Saved state (2 total)
๐ [6] Hello
๐ [7] Hello W
๐ [8] Hello Wo
๐ [7] Hello W
๐ [8] Hello Wr
๐พ Saved state (3 total)
--- Undo ---
โ Restored to 6/9/24, 3:30 PM:
๐ [6] Hello
โ Restored to 6/9/24, 3:30 PM:
๐ [5] Hello
*/
Establishes a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically.
Pattern | Relation Direction | Communication Style | Use Case Example |
---|---|---|---|
Observer | One-to-many (push) | Automatic broadcasts | Weather updates to multiple displays |
Mediator | Many-to-many | Centralized routing | Chat room message distribution |
Delegate | One-to-one | Direct callbacks | UITableView cell configuration |
@Published
in SwiftUI)UINavigationController
)UITableViewDelegate
)
```swift
import Combine// Observable (Subject) class WeatherStation { @Published var temperature: Double = 20.0 // Auto-publishes changes }
// Observer let station = WeatherStation() var cancellable: AnyCancellable?
// Subscribe to changes cancellable = station.$temperature .sink { newTemp in print(โ๐ก๏ธ Temperature now: (newTemp)ยฐCโ) }
// Trigger change station.temperature = 25.0 // Observer prints automatically
/* Output: ๐ก๏ธ Temperature now: 25.0ยฐC */
Key Components:
@Published property - The observable subject
$ prefix access - Creates a publisher
.sink - The observer's subscription
AnyCancellable - Manages observation lifecycle
This is the modern Swift (Combine) way to implement Observer pattern with:
Less boilerplate
Type safety
Built-in memory management
## ๐ State (Behavioral)
The **State pattern** allows an object to alter its behavior when its internal state changes โ it appears as if the object changed its class. It's useful when an object must change behavior at runtime depending on its current state.
### ๐งโ๐ป Swift Code Example
The State pattern is excellent for managing complex logic by encapsulating state-specific behavior into separate objects. This approach makes your code more scalable and easier to maintain than using large `switch` statements or conditional `if/else` logic. A classic example for a scalable app is a **document editor**, which can exist in various states like `Draft`, `Moderation`, and `Published`.
### Context and States
First, we need to define the **context**โthe object whose behavior changes based on its state. We also need to define the **state protocol** and the **concrete states**.
```swift
// State Protocol
// This defines the behavior that all concrete states must implement.
protocol DocumentState: AnyObject {
func publish(in context: Document)
func approve(in context: Document)
func reject(in context: Document)
}
```
-----
### Concrete States
Each state class encapsulates the logic for a specific state. The `publish`, `approve`, and `reject` methods behave differently depending on the current state. The `context` (the `Document` instance) is passed so that the state object can change the document's state.
```swift
// Draft State
class DraftState: DocumentState {
func publish(in context: Document) {
print("Draft is submitted for moderation.")
context.state = ModerationState()
}
func approve(in context: Document) {
print("Cannot approve a draft. It must be published first.")
}
func reject(in context: Document) {
print("Cannot reject a draft. It is not yet in moderation.")
}
}
// Moderation State
class ModerationState: DocumentState {
func publish(in context: Document) {
print("Cannot publish while in moderation. You must approve it first.")
}
func approve(in context: Document) {
print("Document is approved and published.")
context.state = PublishedState()
}
func reject(in context: Document) {
print("Document is rejected and sent back to draft.")
context.state = DraftState()
}
}
// Published State
class PublishedState: DocumentState {
func publish(in context: Document) {
print("Document is already published.")
}
func approve(in context: Document) {
print("Document is already published and does not need approval.")
}
func reject(in context: Document) {
print("Cannot reject a published document.")
}
}
```
-----
### The Context Object
The `Document` class is the **context**. It holds a reference to the current state and delegates all actions to it. This decouples the document's main logic from the state-specific behavior.
```swift
class Document {
var state: DocumentState
init() {
self.state = DraftState() // Initial state
}
func publish() {
state.publish(in: self)
}
func approve() {
state.approve(in: self)
}
func reject() {
state.reject(in: self)
}
}
```
### Usage
The client code interacts with the `Document` object without knowing or caring about its internal state machine. The behavior changes automatically based on the current state.
```swift
let myDocument = Document()
// Initial state: Draft
print("Current State: \(type(of: myDocument.state))") // Prints "DraftState"
myDocument.approve() // "Cannot approve a draft..."
myDocument.publish() // "Draft is submitted for moderation."
// State changed to Moderation
print("Current State: \(type(of: myDocument.state))") // Prints "ModerationState"
myDocument.reject() // "Document is rejected..."
// State changed back to Draft
print("Current State: \(type(of: myDocument.state))") // Prints "DraftState"
myDocument.publish() // "Draft is submitted for moderation."
// State changed to Moderation
print("Current State: \(type(of: myDocument.state))") // Prints "ModerationState"
myDocument.approve() // "Document is approved and published."
// Final State: Published
print("Current State: \(type(of: myDocument.state))") // Prints "PublishedState"
myDocument.publish() // "Document is already published."
```
**iOS and Apple frameworks internally use the State pattern**, especially in scenarios where **an objectโs behavior changes based on its current state**. Below are real-world examples:
---
### โ
1. `URLSessionTask` State Management
Each task (`dataTask`, `uploadTask`, etc.) has a `state`:
```swift
public enum URLSessionTask.State {
case running
case suspended
case canceling
case completed
}
```
- The behavior of the task changes based on this state.
- You cannot resume a completed task or cancel a suspended one โ only allowed transitions are handled.
๐ง **State Pattern:** Each state governs what next action is valid โ mimicking the classic pattern.
---
### โ
2. `UIViewController` Lifecycle
A `UIViewController` transitions through multiple internal states:
- `viewDidLoad`
- `viewWillAppear`
- `viewDidAppear`
- `viewWillDisappear`
- `viewDidDisappear`
Each of these corresponds to different internal "states" and determines what setup or teardown logic runs.
๐ง **State Pattern:** The controller behaves differently depending on its current stage in the lifecycle.
---
### โ
3. `UIButton` States
A `UIButton` can be in:
- `.normal`
- `.highlighted`
- `.disabled`
- `.selected`
```swift
button.setTitle("Submit", for: .normal)
button.setTitle("Submitting...", for: .disabled)
```
Each state defines different behavior and visuals.
๐ง **State Pattern:** UIButton modifies appearance and behavior based on state automatically โ internal implementation leverages State-like behavior.
---
### โ
4. `AVPlayer`
An `AVPlayer` can be:
- Playing
- Paused
- Stopped
- Buffering
Each of these internal states modifies what actions are allowed (e.g., can't pause a stopped player).
---
### ๐ Summary
| Area | iOS API | Pattern Used |
|--------------|-----------------------------|---------------|
| Networking | `URLSessionTask.State` | โ
State |
| UI Lifecycle | `UIViewController` | โ
State |
| UI Controls | `UIButton`, `UISwitch`, etc. | โ
State |
| Media | `AVPlayer` | โ
State |
---
## ๐ฏ Strategy (Behavioral)
The **Strategy Pattern** is used to define a family of algorithms, encapsulate each one, and make them interchangeable. It allows the algorithm to vary independently from clients that use it.
---
### โ
Real-World Analogy
Imagine a **Navigation App** (like Google Maps or Apple Maps). You can choose how you want to reach your destination:
- ๐ **Fastest Route**
- ๐ฆ **Least Traffic**
- ๐ **Public Transport**
- ๐ถ **Walking**
Each of these is a *strategy* for navigation. The app lets you switch strategies at runtime without rewriting the logic of the app itself.
---
### ๐ป Swift Example
```swift
// MARK: - Strategy Protocol
protocol RouteStrategy {
func buildRoute(from: String, to: String)
}
// MARK: - Concrete Strategies
struct FastestRoute: RouteStrategy {
func buildRoute(from: String, to: String) {
print("๐ Fastest route from \(from) to \(to)")
}
}
struct LeastTrafficRoute: RouteStrategy {
func buildRoute(from: String, to: String) {
print("๐ Least traffic route from \(from) to \(to)")
}
}
struct PublicTransportRoute: RouteStrategy {
func buildRoute(from: String, to: String) {
print("๐ Public transport route from \(from) to \(to)")
}
}
// MARK: - Context
class NavigationContext {
private var strategy: RouteStrategy
init(strategy: RouteStrategy) {
self.strategy = strategy
}
func updateStrategy(_ strategy: RouteStrategy) {
self.strategy = strategy
}
func navigate(from: String, to: String) {
strategy.buildRoute(from: from, to: to)
}
}
```
---
### ๐งช Usage
```swift
let nav = NavigationContext(strategy: FastestRoute())
nav.navigate(from: "Home", to: "Work")
nav.updateStrategy(LeastTrafficRoute())
nav.navigate(from: "Home", to: "Work")
nav.updateStrategy(PublicTransportRoute())
nav.navigate(from: "Home", to: "Work")
```
---
### ๐ฆ Benefits
- Promotes **Open/Closed Principle** โ add new strategies without modifying existing code.
- Helps separate concerns โ algorithm logic is decoupled from usage.
- Easily testable and extendable.
---
### ๐ฑ Where It's Used in iOS
- `UICollectionView` / `UITableView` uses different *layout strategies* (`UICollectionViewFlowLayout`, `UICollectionViewCompositionalLayout`)
- `UIViewControllerTransitioningDelegate` allows different transition *animation strategies*
---
<a id="template-method"></a>
## ๐ Template-Method-(Behavioral)
The **Template Method** pattern defines the *skeleton* of an algorithm in a base class, while allowing subclasses to redefine specific steps without changing the algorithm's overall structure.
This promotes **code reuse**, enforces **consistent workflows**, and allows flexibility in extending behavior where needed.
---
### โ
When to Use
- When multiple classes share the same algorithm structure but differ in specific steps.
- When you want to avoid code duplication while allowing extensions.
---
### ๐ Real-World Example
#### ๐ฆ Online Order Processing(test)
```swift
class OrderProcessTemplate {
func processOrder() {
selectItems()
makePayment()
shipItems()
sendReceipt()
}
func selectItems() {
print("Items selected")
}
func makePayment() {
fatalError("Subclass must override makePayment()")
}
func shipItems() {
print("Items shipped")
}
func sendReceipt() {
print("Receipt sent")
}
}
class OnlineOrder: OrderProcessTemplate {
override func makePayment() {
print("Paid using UPI")
}
}
let order = OnlineOrder()
order.processOrder()
UIKit uses the Template Method pattern extensively. For example:
UITableView
Apple defines the structure of rendering a table, but developers customize specific steps via delegate methods:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Apple calls the template lifecycle internally, but gives you hooks to override critical steps.
Role | Description |
---|---|
Abstract Class | Defines the template method and default steps |
Subclass | Overrides one or more steps to provide specific behavior |
โ Helps maintain algorithm consistency while offering flexibility for customization.
The Visitor pattern allows you to add new operations to a group of objects without changing their classes. Instead of adding logic to the objects, you create a separate visitor that implements the operation.
protocol Document {
func accept(visitor: DocumentVisitor)
}
class PDFDocument: Document {
func accept(visitor: DocumentVisitor) {
visitor.visit(pdf: self)
}
}
class WordDocument: Document {
func accept(visitor: DocumentVisitor) {
visitor.visit(word: self)
}
}
protocol DocumentVisitor {
func visit(pdf: PDFDocument)
func visit(word: WordDocument)
}
class PrintVisitor: DocumentVisitor {
func visit(pdf: PDFDocument) {
print("๐จ๏ธ Printing PDF Document")
}
func visit(word: WordDocument) {
print("๐จ๏ธ Printing Word Document")
}
}
let documents: [Document] = [PDFDocument(), WordDocument()]
let printer = PrintVisitor()
for doc in documents {
doc.accept(visitor: printer)
}
While iOS doesnโt explicitly use the Visitor pattern, similar behavior can be seen in:
FileManager.delegate
for performing actions on files/folders.CALayer
, CATextLayer
, CAShapeLayer
) โ traversing view layers and applying logic.Role | Description |
---|---|
Visitor |
Declares visit() methods for each concrete type |
Element |
Has an accept(visitor:) method |
ConcreteVisitor |
Implements actions to be taken on each element |
โ The Visitor pattern promotes open/closed principle by letting you add new operations without modifying existing classes.