תבנית Strategy
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Swift של Coddy. שיעור 45 מתוך 57.
תבנית האסטרטגיה מציבה כל וריאנט של אלגוריתם בטיפוס משלו מאחורי פרוטוקול משותף. הקוד שמשתמש בה מחזיק אסטרטגיה ויכול להחליף אותה בזמן ריצה:
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))פלט:
108
121ב-Swift, לעיתים קרובות closure מספיקה לאסטרטגיה עם מתודה אחת. הטיפוס מאחסן פונקציה ומפעיל אותה:
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))פלט:
["apple", "banana", "kiwi"]
["kiwi", "apple", "banana"]האסטרטגיה מחליפה switch בתוך האלגוריתם. וריאציות חדשות הן טיפוסים חדשים (או סגירות), והקוד שמשתמש בהן נשאר ללא שינוי:
// ללא אסטרטגיה: כל אפשרות חדשה מחייבת שינוי בפונקציה הזו
func cost(kind: String, weight: Int) -> Int {
switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// עם אסטרטגיה: Checkout לעולם אינו משתנה
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }אפשר לבחור אסטרטגיות מתוך נתונים, למשל מתוך הגדרה של משתמש, באמצעות מפעל קטן:
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))פלט:
160
200אתגר
קלדמות במשחק משתמשת באסטרטגיית התקפה. לפרוטוקול Attack יש name ו־damage(strength:) -> Int. ממשו את Sword (strength * 2), את Bow (strength + 5) ואת Magic (עם mana שנשמר; הנזק הוא strength * 3 כאשר mana הוא לפחות 10, ו־1 אחרת; damage עצמו אינו משנה את mana). Hero שומר את strength שלו ואת attack, ו־strike() מחזירה Ada hits with sword for 20.
הקוד שסופק קורא את שם הגיבור ואת הכוח שלו, ואז מקבל פקודות כגון equip sword, equip bow, equip magic 15 או strike. הוא מדפיס את התוצאה של כל תקיפה.
כתבו את הקוד בקבצים Attack.swift, Sword.swift, Bow.swift, Magic.swift ו־Hero.swift. הקובץ main.swift מכיל את קוד הקלט/פלט שסופק, ואי אפשר לערוך אותו.
נסו בעצמכם
// קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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()
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
תרגלו בעצמכם: Swift אונליין