Protección del estado
Parte de la sección Programación orientada a objetos del Journey de Swift de Coddy. Lección 19 de 57.
Un invariante es una regla que siempre debe cumplirse para un valor, como «un saldo nunca es negativo». Un tipo protege sus invariantes comprobando cada cambio antes de aplicarlo:
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)Salida:
false
true
60Cuando quien llama necesita saber por qué se rechazó un cambio, el método puede lanzar un error en lugar de devolver false. El tipo de error es un enum que se ajusta 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")
}Salida:
missing 20Mantén cada cambio de los datos protegidos dentro de métodos que los comprueben. Si la propiedad pudiera establecerse desde fuera, cualquier línea de código podría romper la regla sin que el tipo se diera cuenta:
struct Temperature {
private(set) var kelvin: Double // debe permanecer >= 0
mutating func cool(by amount: Double) {
kelvin = max(0, kelvin - amount) // la única forma de bajarlo
}
}Una operación que cambia varias propiedades debe dejarlas coherentes incluso cuando falla a mitad de camino. Compruébalo todo primero y después cambia el estado:
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"]!)Salida:
false
2
true
1Desafío
FácilCompleta la estructura Seats, que vende entradas para un espectáculo con capacity asientos. sold es private(set). buy(_:) lanza TicketError.invalidCount cuando la cantidad es menor que 1, TicketError.soldOut cuando no queda ningún asiento y TicketError.notEnough(left:) cuando quedan menos asientos de los solicitados; de lo contrario, añade las entradas a sold.
El código proporcionado lee la capacidad y una cantidad solicitada por línea. Para cada una, imprime sold 3, left 7, invalid count, sold out o only 2 left.
Tu código va en TicketError.swift y Seats.swift. main.swift contiene el código proporcionado de entrada/salida y no se puede editar.
Pruébalo tú mismo
// Código de entrada/salida suministrado: déjalo como está
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")
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Clases y objetos
Trabajar con archivosDefinir clasesMétodos y selfInicializadoresLas clases son referenciasRepaso: tarjeta de biblioteca4Encapsulación
Control de accesoSolo lectura desde fueraProtección del estadoInicializadores que pueden fallarRepaso - Cuenta bancaria7Polimorfismo y genéricos
PolimorfismoConversión de tiposFunciones genéricasTipos genéricosRepaso - Calculadora de formas2Structs y tipos por valor
StructsSemántica de valoresMétodos mutablesStruct o claseRepaso - elemento del inventario5Herencia
SubclasesSobrescritura de métodosInicializadores y superfinal y jerarquíasRepaso: jerarquía de empleados8Protocolos estándar
CustomStringConvertibleEquatableComparableHashableRepaso - Ordenamiento personalizado11Proyecto: Gestión de bibliotecas
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Swift