Menu
Coddy logo textTech

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
red

L'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 shipped

Per 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 -> open

Le 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() ?? state
challenge icon

Sfida

Facile

Modella 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)")
    }
}
quiz iconMettiti alla prova

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

Esercitati da solo: Playground Swift