Menu
CoddyTech

Bảo vệ trạng thái

Thuộc phần Lập trình hướng đối tượng trong hành trình Swift của Coddy. Bài học 19 trên 57.

Tính bất biến là quy tắc luôn phải đúng với một giá trị, chẳng hạn như “số dư không bao giờ âm”. Kiểu dữ liệu bảo vệ các tính bất biến bằng cách kiểm tra mọi thay đổi trước khi áp dụng:

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)

Đầu ra:

false
true
60

Khi bên gọi cần biết lý do một thay đổi bị từ chối, phương thức có thể ném ra một lỗi thay vì trả về false. Kiểu lỗi là một enum tuân theo 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")
}

Đầu ra:

missing 20

Đặt mọi thay đổi đối với dữ liệu được bảo vệ bên trong các phương thức kiểm tra dữ liệu đó. Nếu có thể thiết lập thuộc tính từ bên ngoài, bất kỳ dòng code nào cũng có thể phá vỡ quy tắc mà kiểu dữ liệu không nhận ra:

struct Temperature {
    private(set) var kelvin: Double   // phải luôn >= 0
    mutating func cool(by amount: Double) {
        kelvin = max(0, kelvin - amount)   // cách duy nhất để giảm giá trị này
    }
}

Một thao tác thay đổi nhiều thuộc tính phải giữ chúng nhất quán ngay cả khi thất bại giữa chừng. Trước tiên, hãy kiểm tra mọi thứ, sau đó mới thay đổi trạng thái:

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"]!)

Đầu ra:

false
2
true
1
challenge icon

Thử thách

Dễ

Hoàn thiện struct Seats, dùng để bán vé cho một buổi biểu diễn có capacity chỗ ngồi. sold có thuộc tính private(set). buy(_:) ném TicketError.invalidCount khi số lượng nhỏ hơn 1, TicketError.soldOut khi không còn chỗ, và TicketError.notEnough(left:) khi số chỗ còn lại ít hơn số lượng được yêu cầu; nếu không, hàm cộng số vé vào sold.

Code được cung cấp sẽ đọc sức chứa và một số lượng được yêu cầu trên mỗi dòng. Với mỗi dòng, code sẽ in ra sold 3, left 7, invalid count, sold out hoặc only 2 left.

Code của bạn nằm trong TicketError.swift và Seats.swift. main.swift chứa code đầu vào/đầu ra được cung cấp và không thể chỉnh sửa.

Tự làm thử

// Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Swift