Menu
Coddy logo textTech

Protegendo o Estado

Parte da seção Programação Orientada a Objetos do Journey de Swift da Coddy. Lição 19 de 57.

Um invariante é uma regra que deve sempre ser válida para um valor, como “um saldo nunca é negativo”. Um tipo protege seus invariantes verificando cada alteração antes de aplicá-la:

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)

Saída:

false
true
60

Quando quem chama precisa saber por que uma alteração foi recusada, o método pode lançar um erro em vez de retornar false. O tipo do erro é um enum que está em conformidade com 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")
}

Saída:

missing 20

Mantenha toda alteração nos dados protegidos dentro de métodos que os verifiquem. Se a propriedade pudesse ser definida externamente, qualquer linha de código poderia violar a regra sem que o tipo percebesse:

struct Temperature {
    private(set) var kelvin: Double   // deve permanecer >= 0
    mutating func cool(by amount: Double) {
        kelvin = max(0, kelvin - amount)   // a única forma de baixá-lo
    }
}

Uma operação que altera várias propriedades deve mantê-las consistentes mesmo quando falha no meio. Verifique tudo primeiro e, depois, altere o 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"]!)

Saída:

false
2
true
1
challenge icon

Desafio

Fácil

Complete a struct Seats, que vende ingressos para um espetáculo com capacity assentos. sold é private(set). buy(_:) lança TicketError.invalidCount para uma quantidade menor que 1, TicketError.soldOut quando não resta nenhum assento e TicketError.notEnough(left:) quando restam menos assentos do que o solicitado; caso contrário, adiciona os ingressos a sold.

O código fornecido lê a capacidade e uma quantidade solicitada por linha. Para cada uma, ele imprime sold 3, left 7, invalid count, sold out ou only 2 left.

Seu código deve ficar em TicketError.swift e Seats.swift. main.swift contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

// Código de entrada/saída fornecido: mantenha-o 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")
    }
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Playground de Swift