תבנית המצב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Swift של Coddy. שיעור 49 מתוך 57.
תבנית המצב מאפשרת לאובייקט לשנות את ההתנהגות שלו כשהמצב הפנימי שלו משתנה. כל מצב הוא טיפוס בפני עצמו, שמחליט מה יקרה ומה יהיה המצב הבא:
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()
}פלט:
red
green
yellow
redהאובייקט שמחזיק את המצב מעביר בקשות למצב הנוכחי שלו. כל מצב מטפל באירוע בדרכו שלו, ומצבים שאינם מתירים פעולות לא חוקיות מסרבים להן:
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)) }פלט:
cannot ship when new
now paid
cannot pay when paid
now shippedעבור מספר קטן של מצבים ללא נתונים משלהם, switch בתוך enum מבטא לעיתים קרובות את אותו הרעיון בצורה תמציתית יותר, כשהמהדר בודק שכל מצב מטופל:
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)") }
}פלט:
lock -> locked
cannot open
unlock -> closed
open -> openדפוס מצבים משתלם כאשר לכל מצב יש כמה התנהגויות או נתונים משלו. כך או כך, הכללים לכל מצב מרוכזים במקום אחד במקום להיות מפוזרים באובייקט בבדיקות if.
// מפוזר: כל מתודה בודקת את המצב
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// תבנית מצב: כל מצב אחראי לעצמו
state = state.ship() ?? stateאתגר
קלדמה מכונת ממכר באמצעות מצב enum בשם Vending עם המקרים idle, hasCoin(Int) ו-soldOut. handle(_ event:) מחזירה את המצב הבא או nil עבור אירוע שאינו מותר: coin <n> במצב idle עובר ל-hasCoin(n), ובמצב hasCoin מוסיף לסכום; buy במצב hasCoin עם לפחות 150 עובר ל-idle (מכירה מוצלחת); refund במצב hasCoin עובר ל-idle; empty בכל מצב עובר ל-soldOut; refill במצב soldOut עובר ל-idle.
הקוד שסופק מחיל את האירועים לפי הסדר ומדפיס את המצב אחרי כל אירוע (idle, coin 100, sold out) או not allowed: <event>.
הקוד שלך ייכתב בקובץ Vending.swift. הקובץ main.swift מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.
נסו בעצמכם
// קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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)")
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
תרגלו בעצמכם: Swift אונליין