Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط الحالة

جزء من قسم البرمجة كائنية التوجه في رحلة 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
challenge icon

التحدي

سهل

صمّم آلة بيع باستخدام تعداد للحالة 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)")
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Swift