Wzorzec stanu
Część sekcji Programowanie obiektowe ścieżki Swift w Coddy. Lekcja 49 z 57.
Wzorzec stanu pozwala obiektowi zmieniać swoje zachowanie, gdy zmienia się jego stan wewnętrzny. Każdy stan jest własnym typem, który decyduje o tym, co się dzieje i który stan następuje po nim:
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()
}Wynik:
red
green
yellow
redObiekt będący właścicielem stanu przekazuje żądania do swojego bieżącego stanu. Każdy stan obsługuje zdarzenie na swój sposób, a niedozwolone działania są odrzucane przez stany, które na nie nie pozwalają:
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)) }Wynik:
cannot ship when new
now paid
cannot pay when paid
now shippedW przypadku niewielkiej liczby stanów bez własnych danych enum z switch często wyraża tę samą ideę zwięźlej, a kompilator sprawdza, czy obsłużono każdy stan:
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)") }
}Wynik:
lock -> locked
cannot open
unlock -> closed
open -> openTypy stanów sprawdzają się, gdy każdy stan ma kilka zachowań lub własne dane. W obu przypadkach reguły dla każdego stanu znajdują się w jednym miejscu, zamiast być rozproszone w obiekcie w postaci warunków if.
// rozproszone: każda metoda sprawdza stan
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// wzorzec stanu: każdy stan odpowiada za siebie
state = state.ship() ?? stateWyzwanie
ŁatwyZamodeluj automat sprzedający za pomocą stanu wyliczeniowego Vending z przypadkami idle, hasCoin(Int) i soldOut. handle(_ event:) zwraca następny stan lub nil dla niedozwolonego zdarzenia: coin <n> w stanie idle przechodzi do hasCoin(n), a w stanie hasCoin zwiększa kwotę; buy w stanie hasCoin z kwotą co najmniej 150 przechodzi do idle (udana sprzedaż); refund w stanie hasCoin przechodzi do idle; empty w dowolnym stanie przechodzi do soldOut; refill w stanie soldOut przechodzi do idle.
Dostarczony kod przetwarza zdarzenia po kolei i wyświetla stan po każdym z nich (idle, coin 100, sold out) albo not allowed: <event>.
Umieść swój kod w pliku Vending.swift. Plik main.swift zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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)")
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasMetody i selfInicjalizatoryKlasy są referencjamiPowtórzenie — karta biblioteczna4Hermetyzacja
Kontrola dostępuTylko do odczytu z zewnątrzOchrona stanuInicjalizatory, które mogą zawieśćPodsumowanie — konto bankowe7Polimorfizm i typy generyczne
PolimorfizmRzutowanie typówFunkcje generyczneTypy generycznePodsumowanie – kalkulator kształtów2Struktury i typy wartościowe
StrukturySemantyka wartościMetody modyfikująceStruktura czy klasaPodsumowanie – element inwentarza5Dziedziczenie
PodklasyPrzesłanianie metodInicjalizatory i superfinal i hierarchiePodsumowanie — hierarchia pracowników8Standardowe protokoły
CustomStringConvertibleEquatableComparableHashablePodsumowanie — własne sortowanie11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Swift