Padrão de Estado
Parte da seção Programação Orientada a Objetos do Journey de Swift da Coddy. Lição 49 de 57.
O padrão state permite que um objeto mude seu comportamento quando seu estado interno muda. Cada estado é seu próprio tipo, que decide o que acontece e qual estado vem a seguir:
protocol LightState {
var name: String { get }
func next() -> any LightState
}
struct Red: LightState { var name: String { "red" }; func next() -> any LightState { Green() } }
struct Green: LightState { var name: String { "green" }; func next() -> any LightState { Yellow() } }
struct Yellow: LightState { var name: String { "yellow" }; func next() -> any LightState { Red() } }
var light: any LightState = Red()
for _ in 1...4 {
print(light.name)
light = light.next()
}Saída:
red
green
yellow
redO objeto que possui o estado encaminha as solicitações para seu estado atual. Cada estado trata um evento à sua própria maneira, e ações ilegais são recusadas pelos estados que não as permitem:
protocol OrderState {
var name: String { get }
func pay() -> (any OrderState)?
func ship() -> (any OrderState)?
}
struct New: OrderState { var name: String { "new" }; func pay() -> (any OrderState)? { Paid() }; func ship() -> (any OrderState)? { nil } }
struct Paid: OrderState { var name: String { "paid" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { Shipped() } }
struct Shipped: OrderState { var name: String { "shipped" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { nil } }
final class Order {
private(set) var state: any OrderState = New()
func handle(_ event: String) -> String {
guard let next = event == "pay" ? state.pay() : state.ship() else { return "cannot \(event) when \(state.name)" }
state = next
return "now \(state.name)"
}
}
let order = Order()
for e in ["ship", "pay", "pay", "ship"] { print(order.handle(e)) }Saída:
cannot ship when new
now paid
cannot pay when paid
now shippedPara um pequeno número de estados sem dados próprios, um enum com um switch geralmente expressa a mesma ideia de forma mais compacta, com o compilador verificando se todos os estados foram tratados:
enum Door {
case open, closed, locked
func handle(_ action: String) -> Door? {
switch (self, action) {
case (.closed, "open"): return .open
case (.open, "close"): return .closed
case (.closed, "lock"): return .locked
case (.locked, "unlock"): return .closed
default: return nil
}
}
}
var door = Door.closed
for a in ["lock", "open", "unlock", "open"] {
if let next = door.handle(a) { door = next; print("\(a) -> \(door)") } else { print("cannot \(a)") }
}Saída:
lock -> locked
cannot open
unlock -> closed
open -> openOs tipos de estado compensam quando cada estado tem vários comportamentos ou seus próprios dados. De qualquer forma, as regras de cada estado ficam em um único lugar, em vez de verificações com if espalhadas pelo objeto.
// espalhado: cada método verifica o estado
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// padrão state: cada estado responde por si mesmo
state = state.ship() ?? stateDesafio
FácilModele uma máquina de venda automática com um estado enum Vending com os casos idle, hasCoin(Int) e soldOut. handle(_ event:) retorna o próximo estado ou nil para um evento que não é permitido: coin <n> em idle passa para hasCoin(n), e em hasCoin adiciona ao valor; buy em hasCoin com pelo menos 150 passa para idle (uma venda bem-sucedida); refund em hasCoin passa para idle; empty em qualquer estado passa para soldOut; refill em soldOut passa para idle.
O código fornecido aplica os eventos na ordem e imprime o estado após cada um (idle, coin 100, sold out) ou not allowed: <event>.
Seu código deve ficar em Vending.swift. main.swift contém o código de entrada/saída fornecido 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 machine = Vending.idle
for event in input {
if let next = machine.handle(event) {
machine = next
print(machine.label)
} else {
print("not allowed: \(event)")
}
}
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
1Classes e Objetos
Trabalhando com ArquivosDefinindo ClassesMétodos e selfInicializadoresClasses são ReferênciasRevisão - Cartão da Biblioteca4Encapsulamento
Controle de AcessoSomente Leitura ExternaProtegendo o EstadoInicializadores que Podem FalharRevisão - Conta Bancária7Polimorfismo e Genéricos
PolimorfismoConversão de TiposFunções GenéricasTipos GenéricosRevisão - Calculadora de Formas2Structs e Tipos de Valor
StructsSemântica de ValorMétodos MutáveisStruct ou ClasseRecapitulação - Item de Inventário5Herança
SubclassesSobrescrita de métodosInicializadores e superfinal e hierarquiasRecapitulação - Hierarquia de funcionários8Protocolos Padrão
CustomStringConvertibleEquatableComparableHashableRecapitulação - Ordenação Personalizada11Projeto: Gerenciamento de Biblioteca
Livros e MembrosEmpréstimo de LivrosPratique por conta própria: Playground de Swift