Menu
Coddy logo textTech

Pattern Strategy

Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 45 di 57.

Il pattern strategy inserisce ogni variante di un algoritmo nel proprio tipo, dietro un protocollo condiviso. Il codice che lo usa conserva una strategia e può sostituirla durante l’esecuzione:

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, una closure è spesso sufficiente per una strategia con un solo metodo. Il tipo memorizza una funzione e la chiama:

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"]

La strategia sostituisce uno switch all'interno dell'algoritmo. Le nuove varianti sono nuovi tipi (o chiusure) e il codice che le usa rimane invariato:

// senza una strategia: ogni nuova opzione richiede di modificare questa funzione
func cost(kind: String, weight: Int) -> Int {
    switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// con una strategia: Checkout non cambia mai
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }

Le strategie possono essere scelte in base ai dati, ad esempio da un'impostazione dell'utente, con una piccola 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

Sfida

Facile

L'attacco di un personaggio di un gioco è una strategia. Il protocollo Attack ha name e damage(strength:) -> Int. Implementa Sword (strength * 2), Bow (strength + 5) e Magic (un valore mana memorizzato; il danno è strength * 3 quando mana è almeno 10 e 1 altrimenti; damage non modifica mana). Hero memorizza il proprio strength e attack, e strike() restituisce Ada hits with sword for 20.

Il codice fornito legge il nome e la forza dell'eroe, poi i comandi equip sword, equip bow, equip magic 15 o strike. Stampa il risultato di ogni attacco.

Il tuo codice va inserito in Attack.swift, Sword.swift, Bow.swift, Magic.swift e Hero.swift. main.swift contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

// Codice di input/output fornito: lascialo così com'è
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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Playground Swift