Menu
Coddy logo textTech

Strategy Pattern

Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 45 of 57.

The strategy pattern puts each variant of an algorithm in its own type behind a shared protocol. The code that uses it holds a strategy and can swap it at run time:

protocol ShippingStrategy {
    func cost(weight: Int) -> Int
}
struct Standard: ShippingStrategy { func cost(weight: Int) -> Int { 5 + weight } }
struct Express: ShippingStrategy { func cost(weight: Int) -> Int { 15 + weight * 2 } }

struct Checkout {
    var shipping: any ShippingStrategy
    func total(price: Int, weight: Int) -> Int { price + shipping.cost(weight: weight) }
}

var c = Checkout(shipping: Standard())
print(c.total(price: 100, weight: 3))
c.shipping = Express()
print(c.total(price: 100, weight: 3))

Output:

108
121

In Swift, a closure is often enough for a strategy with one method. The type stores a function and calls it:

struct Sorter {
    var rule: (String, String) -> Bool
    func sort(_ words: [String]) -> [String] { words.sorted(by: rule) }
}

let words = ["banana", "kiwi", "apple"]
var s = Sorter(rule: <)
print(s.sort(words))
s.rule = { $0.count < $1.count }
print(s.sort(words))

Output:

["apple", "banana", "kiwi"]
["kiwi", "apple", "banana"]

The strategy replaces a switch inside the algorithm. New variants are new types (or closures), and the code that uses them stays unchanged:

// without a strategy: every new option edits this function
func cost(kind: String, weight: Int) -> Int {
    switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// with a strategy: Checkout never changes
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }

Strategies can be chosen from data, for example from a user setting, with a small factory:

protocol Discount { func apply(_ price: Int) -> Int }
struct NoDiscount: Discount { func apply(_ p: Int) -> Int { p } }
struct Percent: Discount { let rate: Int; func apply(_ p: Int) -> Int { p - p * rate / 100 } }

func discount(for member: String) -> any Discount {
    member == "gold" ? Percent(rate: 20) : NoDiscount()
}

print(discount(for: "gold").apply(200))
print(discount(for: "basic").apply(200))

Output:

160
200
challenge icon

Challenge

Easy

A game character's attack is a strategy. The protocol Attack has name and damage(strength:) -> Int. Implement Sword (strength * 2), Bow (strength + 5) and Magic (a stored mana; damage strength * 3 while mana is at least 10 and 1 otherwise; damage itself does not change mana). Hero stores its strength and attack, and strike() returns Ada hits with sword for 20.

The supplied code reads the hero's name and strength, then commands equip sword, equip bow, equip magic 15 or strike. It prints the result of each strike.

Your code goes in Attack.swift, Sword.swift, Bow.swift, Magic.swift and Hero.swift. main.swift holds the supplied input/output code and cannot be edited.

Try it yourself

// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
let head = input[0].split(separator: ",").map(String.init)
var hero = Hero(name: head[0], strength: Int(head[1])!, attack: Sword())
for cmd in input.dropFirst() {
    let p = cmd.split(separator: " ").map(String.init)
    if p[0] == "strike" { print(hero.strike()); continue }
    switch p[1] {
    case "bow": hero.attack = Bow()
    case "magic": hero.attack = Magic(mana: Int(p[2])!)
    default: hero.attack = Sword()
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Swift playground