Menu
Coddy logo textTech

Strategiemuster

Teil des Abschnitts Objektorientierte Programmierung der Swift-Journey von Coddy. Lektion 45 von 57.

Das Strategy-Muster legt jede Variante eines Algorithmus in einen eigenen Typ hinter einem gemeinsamen Protokoll. Der Code, der es verwendet, enthält eine Strategie und kann sie zur Laufzeit austauschen:

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))

Ausgabe:

108
121

In Swift reicht für eine Strategie mit einer Methode oft eine Closure aus. Der Typ speichert eine Funktion und ruft sie auf:

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))

Ausgabe:

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

Die Strategie ersetzt einen switch innerhalb des Algorithmus. Neue Varianten sind neue Typen (oder Closures), und der Code, der sie verwendet, bleibt unverändert:

// ohne Strategie: jede neue Option ändert diese Funktion
func cost(kind: String, weight: Int) -> Int {
    switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// mit Strategie: Checkout ändert sich nie
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }

Strategien können aus Daten ausgewählt werden, zum Beispiel aus einer Benutzereinstellung, mit einer kleinen 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))

Ausgabe:

160
200
challenge icon

Aufgabe

Einfach

Der Angriff eines Spielfigurcharakters ist eine Strategie. Das Protokoll Attack verfügt über name und damage(strength:) -> Int. Implementiere Sword (strength * 2), Bow (strength + 5) und Magic (ein gespeichertes mana; der Schaden beträgt strength * 3, solange mana mindestens 10 beträgt, und andernfalls 1; damage selbst ändert mana nicht). Hero speichert seine strength und seinen attack, und strike() gibt Ada hits with sword for 20 zurück.

Der bereitgestellte Code liest den Namen und die Stärke des Helden ein und verarbeitet anschließend die Befehle equip sword, equip bow, equip magic 15 oder strike. Er gibt das Ergebnis jedes Angriffs aus.

Dein Code gehört in Attack.swift, Sword.swift, Bow.swift, Magic.swift und Hero.swift. main.swift enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

// Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Swift-Playground