نمط الحالة
جزء من قسم البرمجة كائنية التوجه في رحلة 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بالنسبة إلى عدد صغير من الحالات التي لا تملك بيانات خاصة بها، غالبًا ما يعبّر enum مع switch عن الفكرة نفسها بصورة أكثر اختصارًا، مع تحقّق المصرّف من معالجة كل حالة:
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 المتناثرة في أنحاء الكائن.
// مبعثر: كل method يتحقق من الحالة
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// نمط الحالة: كل حالة تجيب عن نفسها
state = state.ship() ?? stateالتحدي
سهلصمّم آلة بيع باستخدام تعداد للحالة 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)")
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الأصناف والكائنات
العمل مع الملفاتتعريف الأصنافالأساليب وselfالمُهيِّئاتالأصناف هي مراجعمراجعة - بطاقة المكتبة4التغليف
التحكم في الوصولللقراءة فقط من الخارجحماية الحالةالمهيئات القابلة للفشلمراجعة - الحساب البنكي7تعدد الأشكال والأنواع العامة
تعدد الأشكالتحويل الأنواعالدوال العامةالأنواع العامةمراجعة - حاسبة الأشكالتدرّب بنفسك: Playground لـ Swift