תבנית האצלה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Swift של Coddy. שיעור 46 מתוך 57.
האצלה מאפשרת לאובייקט להעביר חלק מההחלטות לאובייקט אחר, הנציג שלו, באמצעות פרוטוקול. האובייקט מבצע את תפקידו ופונה לנציג בנקודות שבהן ההתנהגות משתנה. המסגרות של Apple משתמשות בתבנית הזאת בכל מקום:
protocol DownloadDelegate: AnyObject {
func progress(_ percent: Int)
func finished(_ file: String)
}
final class Downloader {
weak var delegate: DownloadDelegate?
func download(_ file: String) {
for p in [50, 100] { delegate?.progress(p) }
delegate?.finished(file)
}
}
final class Screen: DownloadDelegate {
func progress(_ percent: Int) { print("\(percent)%") }
func finished(_ file: String) { print("done: \(file)") }
}
let screen = Screen()
let d = Downloader()
d.delegate = screen
d.download("photo.png")פלט:
50%
100%
done: photo.pngמאפיין הנציג הוא אופציונלי, והקריאות משתמשות ב־delegate?., כך שהאובייקט פועל גם כשאף אחד לא מאזין. הוא מוכרז כ־weak, כך שהאובייקט לא משאיר את הנציג שלו בחיים בעצמו, וכך נמנעים ממעגלי שימור בין אובייקטים שמפנים זה לזה:
weak var delegate: DownloadDelegate? // דורש שהפרוטוקול יוגבל למחלקות בלבד: AnyObject
delegate?.finished(file) // לא עושה דבר כשאין delegateנציג יכול גם לענות על שאלות, וכך לאפשר לו להתאים אישית את ההתנהגות של האובייקט בלי ליצור מחלקת משנה:
protocol ListDelegate: AnyObject {
func shouldShow(_ item: String) -> Bool
}
final class ListView {
weak var delegate: ListDelegate?
func render(_ items: [String]) -> [String] {
items.filter { delegate?.shouldShow($0) ?? true }
}
}
final class HideDrafts: ListDelegate {
func shouldShow(_ item: String) -> Bool { !item.hasPrefix("draft") }
}
let view = ListView()
let rules = HideDrafts()
print(view.render(["a", "draft b"]))
view.delegate = rules
print(view.render(["a", "draft b"]))פלט:
["a", "draft b"]
["a"]האצלה שונה מדפוס הצופה: לאובייקט יש נציג אחד שהוא פונה אליו ומעדכן אותו, בעוד שלנושא יכולים להיות צופים רבים שהוא רק מעדכן.
// האצלה: שותף אחד, יכול לענות על שאלות
weak var delegate: TableDelegate?
// צופה: מספר בלתי מוגבל של מאזינים, מקבלים הודעות בלבד
private var observers: [(Event) -> Void] = []אתגר
קלCheckout מבקשת מהנציג שלה לקבל החלטות. לפרוטוקול CheckoutDelegate (למחלקות בלבד) יש את discount(for total: Int) -> Int ואת didComplete(total: Int). ל-Checkout יש weak var delegate, והפונקציה pay(_ prices: [Int]) -> Int מסכמת את המחירים, מחסירה את ההנחה של הנציג (0 ללא נציג), מודיעה לנציג על הסכום הסופי ומחזירה אותו. ממשו את LoyaltyShop, נציג שמעניק הנחה של 10% על סכומים של 100 ומעלה ומתעד כל סכום שהושלם ב-receipts.
הקוד שסופק קורא שורות של מחירים המופרדים בפסיקים. הוא מבצע תשלום עבור השורה הראשונה ללא נציג, ועבור השורות האחרות עם LoyaltyShop, מדפיס כל סכום ולבסוף receipts: 120 90.
הקוד שלכם ייכתב בקבצים CheckoutDelegate.swift, Checkout.swift ו-LoyaltyShop.swift. הקובץ main.swift מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.
נסו בעצמכם
// קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
var input: [String] = []
while let line = readLine() { input.append(line) }
let checkout = Checkout()
let shop = LoyaltyShop()
for (i, line) in input.enumerated() {
if i == 1 { checkout.delegate = shop }
print(checkout.pay(line.split(separator: ",").map { Int($0)! }))
}
print("receipts: " + shop.receipts.map { String($0) }.joined(separator: " "))
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
תרגלו בעצמכם: Swift אונליין