Menu
CoddyTech

保护状态

本课属于 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
challenge icon

挑战

简单

完成 Seats 结构体,为一场演出出售门票,座位容量为 capacity。sold 是 private(set)。buy(_:) 在数量小于 1 时抛出 TicketError.invalidCount,没有剩余座位时抛出 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")
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Swift 在线编辑器