状態を守る
CoddyのSwiftジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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チャレンジ
簡単ショーのチケットを販売する capacity 席の Seats 構造体を完成させてください。sold は private(set) です。buy(_:) は、1 未満の件数に対して TicketError.invalidCount を、空席がない場合に TicketError.soldOut を、要求された席数より残りの席数が少ない場合に TicketError.notEnough(left:) をスローします。それ以外の場合は、チケットを sold に追加します。
用意されたコードは、容量と、1 行につき 1 件の要求数を読み取ります。それぞれについて、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プレイグラウンド