Menu
Coddy logo textTech

Wzorzec strategii

Część sekcji Programowanie obiektowe ścieżki Swift w Coddy. Lekcja 45 z 57.

Wzorzec strategii umieszcza każdy wariant algorytmu we własnym typie, który implementuje wspólny protokół. Kod, który z niego korzysta, przechowuje strategię i może ją zmienić w czasie działania programu:

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

Wynik:

108
121

W Swift do strategii z jedną metodą często wystarczy domknięcie. Typ przechowuje funkcję i ją wywołuje:

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

Wynik:

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

Strategia zastępuje switch wewnątrz algorytmu. Nowe warianty to nowe typy (lub domknięcia), a kod, który z nich korzysta, pozostaje niezmieniony:

// bez strategii: każda nowa opcja wymaga zmiany tej funkcji
func cost(kind: String, weight: Int) -> Int {
    switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// ze strategią: Checkout nigdy się nie zmienia
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }

Strategie można wybierać na podstawie danych, na przykład ustawienia użytkownika, za pomocą prostej fabryki:

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

Wynik:

160
200
challenge icon

Wyzwanie

Łatwy

Atak postaci w grze to strategia. Protokół Attack ma name i damage(strength:) -> Int. Zaimplementuj Sword (strength * 2), Bow (strength + 5) i Magic (przechowującą mana; zadającą obrażenia równe strength * 3, gdy mana wynosi co najmniej 10, a w przeciwnym razie 1; samo damage nie zmienia many). Hero przechowuje strength i attack, a strike() zwraca Ada hits with sword for 20.

Udostępniony kod odczytuje imię i siłę bohatera, a następnie polecenia equip sword, equip bow, equip magic 15 lub strike. Wypisuje wynik każdego ataku.

Twój kod umieść w plikach Attack.swift, Sword.swift, Bow.swift, Magic.swift i Hero.swift. main.swift zawiera udostępniony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Edytor online Swift