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
60Quando 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 20Mantieni 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
1Sfida
FacileCompleta 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")
}
}
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
1Classi e oggetti
Lavorare con i fileDefinire le classiMetodi e selfInizializzatoriLe classi sono riferimentiRiepilogo - Tessera della biblioteca4Incapsulamento
Controllo degli accessiSola lettura dall’esternoProteggere lo statoInizializzatori che possono fallireRiepilogo - Conto bancario7Polimorfismo e tipi generici
PolimorfismoCasting dei tipiFunzioni genericheTipi genericiRipasso - Calcolatore di forme2Struct e tipi valore
Le structSemantica dei valoriMetodi mutantiStruct o classeRiepilogo - Articolo di inventario5Ereditarietà
SottoclassiSovrascrivere i metodiInizializzatori e superfinal e gerarchieRiepilogo - Gerarchia dei dipendenti8Protocolli standard
CustomStringConvertibleEquatableComparableHashableRiepilogo - Ordinamento personalizzato11Progetto: Gestione della biblioteca
Libri e utentiPrestito dei libriEsercitati da solo: Playground Swift