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

حماية الحالة

جزء من قسم البرمجة كائنية التوجه في رحلة Swift على Coddy. الدرس 19 من 57.

إن الثابت هو قاعدة يجب أن تتحقق دائمًا لقيمة ما، مثل «الرصيد لا يكون سالبًا أبدًا». يحمي النوع ثوابته من خلال التحقق من كل تغيير قبل تطبيقه:

struct BankAccount {
    private(set) var balance = 0

    mutating func withdraw(_ amount: Int) -> Bool {
        guard amount > 0, amount <= balance else { return false }
        balance -= amount
        return true
    }

    mutating func deposit(_ amount: Int) {
        guard amount > 0 else { return }
        balance += amount
    }
}

var acc = BankAccount()
acc.deposit(100)
print(acc.withdraw(150))
print(acc.withdraw(40))
print(acc.balance)

الناتج:

false
true
60

عندما يحتاج المستدعي إلى معرفة سبب رفض التغيير، يمكن للطريقة أن ترمي خطأً بدلًا من إرجاع false. نوع الخطأ هو تعداد يتوافق مع Error:

enum TransferError: Error {
    case invalidAmount
    case insufficientFunds(missing: Int)
}

struct Account {
    private(set) var balance: Int

    mutating func withdraw(_ amount: Int) throws {
        guard amount > 0 else { throw TransferError.invalidAmount }
        guard amount <= balance else { throw TransferError.insufficientFunds(missing: amount - balance) }
        balance -= amount
    }
}

var acc = Account(balance: 30)
do {
    try acc.withdraw(50)
} catch TransferError.insufficientFunds(let missing) {
    print("missing \(missing)")
} catch {
    print("other error")
}

الناتج:

missing 20

أبقِ كل تغيير في البيانات المحمية داخل أساليب تتحقق منها. لو كانت الخاصية قابلة للتعيين من الخارج، لأمكن لأي سطر من التعليمات البرمجية أن يخالف القاعدة دون أن يلاحظ النوع ذلك:

struct Temperature {
    private(set) var kelvin: Double   // يجب أن يبقى >= 0
    mutating func cool(by amount: Double) {
        kelvin = max(0, kelvin - amount)   // الطريقة الوحيدة لخفضه
    }
}

يجب أن تترك العملية التي تغيّر عدة خصائص هذه الخصائص متسقة حتى عندما تفشل في منتصف الطريق. تحقّق من كل شيء أولًا، ثم غيّر الحالة:

struct Inventory {
    private(set) var stock: [String: Int] = ["tea": 2, "jam": 1]

    mutating func sell(_ items: [String]) -> Bool {
        var needed: [String: Int] = [:]
        for i in items { needed[i, default: 0] += 1 }
        for (item, n) in needed where (stock[item] ?? 0) < n { return false }
        for (item, n) in needed { stock[item]! -= n }
        return true
    }
}

var inv = Inventory()
print(inv.sell(["tea", "jam", "jam"]))
print(inv.stock["tea"]!)
print(inv.sell(["tea", "jam"]))
print(inv.stock["tea"]!)

الناتج:

false
2
true
1
challenge icon

التحدي

سهل

أكمل البنية Seats التي تبيع تذاكر لعرض يتسع لـ capacity من المقاعد. الخاصية sold هي private(set). تُطلق buy(_:) الخطأ TicketError.invalidCount عندما يكون العدد أقل من 1، وTicketError.soldOut عندما لا يبقى أي مقعد، وTicketError.notEnough(left:) عندما يكون عدد المقاعد المتبقية أقل من العدد المطلوب؛ وإلا فإنها تضيف التذاكر إلى sold.

يقرأ الكود المرفق السعة وعددًا مطلوبًا واحدًا في كل سطر. ولكل عدد، يطبع sold 3, left 7 أو invalid count أو sold out أو only 2 left.

ضع الكود الخاص بك في TicketError.swift وSeats.swift. يحتوي main.swift على كود الإدخال/الإخراج المرفق، ولا يمكن تعديله.

جرّب بنفسك

// كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
var input: [String] = []
while let line = readLine() { input.append(line) }
var seats = Seats(capacity: Int(input[0])!)
for line in input.dropFirst() {
    let n = Int(line)!
    do {
        try seats.buy(n)
        print("sold \(n), left \(seats.capacity - seats.sold)")
    } catch TicketError.invalidCount {
        print("invalid count")
    } catch TicketError.soldOut {
        print("sold out")
    } catch TicketError.notEnough(let left) {
        print("only \(left) left")
    } catch {
        print("unexpected")
    }
}
quiz iconاختبر نفسك

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

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

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