Patrón de estado
Parte de la sección Programación orientada a objetos del Journey de Swift de Coddy. Lección 49 de 57.
El patrón de estado permite que un objeto cambie su comportamiento cuando cambia su estado interno. Cada estado es su propio tipo, que decide qué sucede y cuál es el siguiente estado:
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()
}Salida:
red
green
yellow
redEl objeto que posee el estado reenvía las solicitudes a su estado actual. Cada estado gestiona un evento a su manera, y los estados que no permiten determinadas acciones rechazan las acciones ilegales:
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)) }Salida:
cannot ship when new
now paid
cannot pay when paid
now shippedPara un número reducido de estados sin datos propios, un enum con un switch suele expresar la misma idea de forma más compacta, y el compilador comprueba que se gestione cada estado:
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)") }
}Salida:
lock -> locked
cannot open
unlock -> closed
open -> openLos tipos de estado resultan útiles cuando cada estado tiene varios comportamientos o sus propios datos. De cualquier manera, las reglas de cada estado permanecen en un solo lugar en vez de estar en comprobaciones if repartidas por todo el objeto.
// disperso: cada método comprueba el estado
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// patrón de estado: cada estado responde por sí mismo
state = state.ship() ?? stateDesafío
FácilModela una máquina expendedora con un estado enum Vending con los casos idle, hasCoin(Int) y soldOut. handle(_ event:) devuelve el siguiente estado o nil para un evento no permitido: coin <n> en idle pasa a hasCoin(n), y en hasCoin suma al importe; buy en hasCoin con al menos 150 pasa a idle (una venta correcta); refund en hasCoin pasa a idle; empty en cualquier estado pasa a soldOut; refill en soldOut pasa a idle.
El código proporcionado aplica los eventos en orden e imprime el estado después de cada uno (idle, coin 100, sold out) o not allowed: <event>.
Tu código va en Vending.swift. main.swift contiene el código de entrada/salida proporcionado y no se puede editar.
Pruébalo tú mismo
// Código de entrada/salida suministrado: déjalo tal 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 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