Pattern State
Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 49 di 57.
Il pattern state permette a un oggetto di cambiare comportamento quando cambia il suo stato interno. Ogni stato è un tipo a sé che decide cosa succede e quale stato viene dopo:
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()
}Output:
red
green
yellow
redL'oggetto che possiede lo stato inoltra le richieste al suo stato attuale. Ogni stato gestisce un evento a modo suo e le azioni non consentite vengono rifiutate dagli stati che non le permettono:
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)) }Output:
cannot ship when new
now paid
cannot pay when paid
now shippedPer un numero ridotto di stati senza dati propri, un enum con uno switch spesso esprime la stessa idea in modo più compatto, con il compilatore che verifica che ogni stato venga gestito:
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)") }
}Output:
lock -> locked
cannot open
unlock -> closed
open -> openLe classi di stato sono utili quando ogni stato ha diversi comportamenti o dati propri. In entrambi i casi, le regole per ogni stato sono raccolte in un unico posto, invece di avere controlli if sparsi nell'oggetto.
// sparpagliato: ogni metodo controlla lo stato
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// pattern State: ogni stato risponde per sé
state = state.ship() ?? stateSfida
FacileModella un distributore automatico con uno stato enum Vending con i casi idle, hasCoin(Int) e soldOut. handle(_ event:) restituisce lo stato successivo oppure nil per un evento non consentito: coin <n> in idle passa a hasCoin(n), mentre in hasCoin aggiunge all’importo; buy in hasCoin con almeno 150 passa a idle (una vendita riuscita); refund in hasCoin passa a idle; empty in qualsiasi stato passa a soldOut; refill in soldOut passa a idle.
Il codice fornito applica gli eventi in ordine e stampa lo stato dopo ciascuno (idle, coin 100, sold out) oppure not allowed: <event>.
Il tuo codice va in Vending.swift. main.swift contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
// Código de entrada/salida proporcionado: mantenlo 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)")
}
}
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