Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der Swift-Journey von Coddy. Lektion 49 von 57.
Das State-Muster ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Jeder Zustand ist ein eigener Typ, der entscheidet, was passiert und welcher Zustand als Nächstes folgt:
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()
}Ausgabe:
red
green
yellow
redDas Objekt, dem der Zustand gehört, leitet Anfragen an seinen aktuellen Zustand weiter. Jeder Zustand verarbeitet ein Ereignis auf seine eigene Weise, und unzulässige Aktionen werden von den Zuständen abgelehnt, die sie nicht erlauben:
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)) }Ausgabe:
cannot ship when new
now paid
cannot pay when paid
now shippedFür eine kleine Anzahl von Zuständen ohne eigene Daten drückt ein Enum mit einem switch dieselbe Idee oft kompakter aus, wobei der Compiler überprüft, ob jeder Zustand behandelt wird:
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)") }
}Ausgabe:
lock -> locked
cannot open
unlock -> closed
open -> openZustandstypen machen sich bezahlt, wenn jeder Zustand mehrere Verhaltensweisen oder eigene Daten hat. In jedem Fall befinden sich die Regeln für jeden Zustand an einer Stelle, anstatt dass sich if-Prüfungen über das Objekt verteilen.
// verstreut: jede Methode prüft den Zustand
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// Zustandsmuster: jeder Zustand antwortet für sich selbst
state = state.ship() ?? stateAufgabe
EinfachModelliere einen Verkaufsautomaten mit einem Enum-Zustand Vending und den Fällen idle, hasCoin(Int) und soldOut. handle(_ event:) gibt den nächsten Zustand oder nil für ein nicht erlaubtes Ereignis zurück: coin <n> in idle führt zu hasCoin(n), und in hasCoin wird der Betrag erhöht; buy in hasCoin führt bei mindestens 150 zu idle (ein erfolgreicher Verkauf); refund in hasCoin führt zu idle; empty führt in jedem Zustand zu soldOut; refill in soldOut führt zu idle.
Der bereitgestellte Code wendet die Ereignisse der Reihe nach an und gibt nach jedem Ereignis den Zustand (idle, coin 100, sold out) oder not allowed: <event> aus.
Dein Code gehört in Vending.swift. main.swift enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.
Probier es selbst
// Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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)")
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenMethoden und selfInitialisiererKlassen sind ReferenzenRückblick – Bibliotheksausweis4Kapselung
ZugriffskontrolleVon außen schreibgeschütztZustand schützenFehlschlagende InitialisiererRückblick – Bankkonto7Polymorphismus und Generika
PolymorphismusTypumwandlungGenerische FunktionenGenerische TypenZusammenfassung – Shape-Rechner2Strukturen und Werttypen
StrukturenWertsemantikVerändernde MethodenStruktur oder KlasseZusammenfassung – Inventarelement5Vererbung
UnterklassenMethoden überschreibenInitialisierer und superfinal und HierarchienRückblick – Mitarbeiterhierarchie8Standardprotokolle
CustomStringConvertibleEquatableComparableHashableRückblick – Benutzerdefiniertes Sortieren11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Swift-Playground