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
121In 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
200Challenge
EasyA 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()
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesMethods and selfInitializersClasses Are ReferencesRecap - Library Card4Encapsulation
Access ControlRead-Only From OutsideGuarding StateFailable InitializersRecap - Bank Account7Polymorphism and Generics
PolymorphismType CastingGeneric FunctionsGeneric TypesRecap - Shape Calculator5Inheritance
SubclassesOverriding MethodsInitializers and superfinal and HierarchiesRecap - Employee Hierarchy11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Swift playground