Menu
Coddy logo textTech

Proteggere lo stato

Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 19 di 57.

Un invariante è una regola che deve essere sempre rispettata per un valore, come «un saldo non è mai negativo». Un tipo protegge i suoi invarianti controllando ogni modifica prima di applicarla:

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)

Output:

false
true
60

Quando chi effettua una chiamata ha bisogno di sapere perché una modifica è stata rifiutata, il metodo può generare un errore invece di restituire false. Il tipo di errore è un enum che è conforme a 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")
}

Output:

missing 20

Mantieni ogni modifica dei dati protetti all’interno dei metodi che li controllano. Se la proprietà fosse modificabile dall’esterno, qualsiasi riga di codice potrebbe violare la regola senza che il tipo se ne accorga:

struct Temperature {
    private(set) var kelvin: Double   // deve rimanere >= 0
    mutating func cool(by amount: Double) {
        kelvin = max(0, kelvin - amount)   // l'unico modo per abbassarla
    }
}

Un'operazione che modifica diverse proprietà deve mantenerle coerenti anche quando non riesce a metà. Controlla tutto prima, poi modifica lo stato:

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

Output:

false
2
true
1
challenge icon

Sfida

Facile

Completa la struct Seats, che vende biglietti per uno spettacolo con capacity posti. sold è private(set). buy(_:) genera TicketError.invalidCount per un numero inferiore a 1, TicketError.soldOut quando non rimane alcun posto e TicketError.notEnough(left:) quando rimangono meno posti di quelli richiesti; altrimenti aggiunge i biglietti a sold.

Il codice fornito legge la capienza e un numero di posti richiesti per ogni riga. Per ciascuno stampa sold 3, left 7, invalid count, sold out o only 2 left.

Il tuo codice va inserito in TicketError.swift e Seats.swift. main.swift contiene il codice fornito per l'input/output e non può essere modificato.

Provalo tu

// Codice di input/output fornito: lascialo così com'è
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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Playground Swift