הגנה על המצב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Swift של Coddy. שיעור 19 מתוך 57.
אינווריאנט הוא כלל שחייב להתקיים תמיד עבור ערך, כגון „יתרה לעולם אינה שלילית”. טיפוס מגן על האינווריאנטים שלו באמצעות בדיקה של כל שינוי לפני החלתו:
struct BankAccount {
private(set) var balance = 0
mutating func withdraw(_ amount: Int) -> Bool {
guard amount > 0, amount <= balance else { return false }
balance -= amount
return true
}
mutating func deposit(_ amount: Int) {
guard amount > 0 else { return }
balance += amount
}
}
var acc = BankAccount()
acc.deposit(100)
print(acc.withdraw(150))
print(acc.withdraw(40))
print(acc.balance)פלט:
false
true
60כאשר מי שקורא לפונקציה צריך לדעת מדוע שינוי נדחה, המתודה יכולה לזרוק שגיאה במקום להחזיר false. סוג השגיאה הוא enum שתואם ל-Error:
enum TransferError: Error {
case invalidAmount
case insufficientFunds(missing: Int)
}
struct Account {
private(set) var balance: Int
mutating func withdraw(_ amount: Int) throws {
guard amount > 0 else { throw TransferError.invalidAmount }
guard amount <= balance else { throw TransferError.insufficientFunds(missing: amount - balance) }
balance -= amount
}
}
var acc = Account(balance: 30)
do {
try acc.withdraw(50)
} catch TransferError.insufficientFunds(let missing) {
print("missing \(missing)")
} catch {
print("other error")
}פלט:
missing 20שמרו כל שינוי בנתונים המוגנים בתוך מתודות שבודקות אותם. אם אפשר היה לשנות את ערך המאפיין מבחוץ, כל שורת קוד הייתה יכולה להפר את הכלל בלי שהטיפוס יבחין בכך:
struct Temperature {
private(set) var kelvin: Double // חייב להישאר >= 0
mutating func cool(by amount: Double) {
kelvin = max(0, kelvin - amount) // הדרך היחידה להוריד אותו
}
}פעולה שמשנה כמה מאפיינים חייבת להשאיר אותם עקביים גם אם היא נכשלת באמצע. בדוק הכול קודם, ואז שנה את המצב:
struct Inventory {
private(set) var stock: [String: Int] = ["tea": 2, "jam": 1]
mutating func sell(_ items: [String]) -> Bool {
var needed: [String: Int] = [:]
for i in items { needed[i, default: 0] += 1 }
for (item, n) in needed where (stock[item] ?? 0) < n { return false }
for (item, n) in needed { stock[item]! -= n }
return true
}
}
var inv = Inventory()
print(inv.sell(["tea", "jam", "jam"]))
print(inv.stock["tea"]!)
print(inv.sell(["tea", "jam"]))
print(inv.stock["tea"]!)פלט:
false
2
true
1אתגר
קלהשלם את המבנה Seats, שמוכר כרטיסים להופעה עם capacity מקומות. sold הוא private(set). buy(_:) זורקת את TicketError.invalidCount עבור מספר קטן מ־1, את TicketError.soldOut כשלא נותר אף מקום, ואת TicketError.notEnough(left:) כשנותרו פחות מקומות מהמספר שהתבקש; אחרת היא מוסיפה את הכרטיסים ל־sold.
הקוד שסופק קורא את הקיבולת ומספר מבוקש אחד בכל שורה. עבור כל אחד מהם הוא מדפיס sold 3, left 7, invalid count, sold out או only 2 left.
הקוד שלך צריך להיכתב בקבצים TicketError.swift ו־Seats.swift. הקובץ main.swift מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.
נסו בעצמכם
// קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
var input: [String] = []
while let line = readLine() { input.append(line) }
var seats = Seats(capacity: Int(input[0])!)
for line in input.dropFirst() {
let n = Int(line)!
do {
try seats.buy(n)
print("sold \(n), left \(seats.capacity - seats.sold)")
} catch TicketError.invalidCount {
print("invalid count")
} catch TicketError.soldOut {
print("sold out")
} catch TicketError.notEnough(let left) {
print("only \(left) left")
} catch {
print("unexpected")
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
תרגלו בעצמכם: Swift אונליין