Защита состояния
Часть раздела Объектно-ориентированное программирование путешествия по 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. Тип ошибки — перечисление, соответствующее 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")
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовМетоды и selfИнициализаторыКлассы — это ссылкиПовторение — читательский билет4Инкапсуляция
Контроль доступаТолько чтение извнеЗащита состоянияИнициализаторы с возможностью сбояПовторение — банковский счёт7Полиморфизм и обобщения
ПолиморфизмПриведение типовОбобщённые функцииОбобщённые типыПовторение — калькулятор фигур10Паттерны проектирования. Часть 2
Паттерн делегированияПаттерн декораторПаттерн строительПаттерн состояния2Структуры и типы-значения
СтруктурыСемантика значенийИзменяющие методыСтруктура или классПовторение — элемент инвентаря5Наследование
ПодклассыПереопределение методовИнициализаторы и superfinal и иерархииПовторение — иерархия сотрудников8Стандартные протоколы
CustomStringConvertibleEquatableComparableHashableПовторение — пользовательская сортировка11Проект: Управление библиотекой
Книги и читателиВыдача книгПотренируйтесь самостоятельно: Песочница Swift