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

نمط Singleton

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

إن نمط التصميم هو حل مُثبت لمشكلة تصميم شائعة. يضمن نمط singleton أن يكون للفئة مثيل واحد بالضبط، ويمكن الوصول إليه من أي مكان. في Swift، يكون ذلك باستخدام static let للاحتفاظ بالمثيل، بالإضافة إلى private init حتى لا يتمكن أحد من إنشاء مثيل آخر:

final class Settings {
    static let shared = Settings()
    var theme = "light"

    private init() {}
}

Settings.shared.theme = "dark"
print(Settings.shared.theme)

الناتج:

dark

يشير كل استخدام لـ Settings.shared إلى الكائن نفسه، لذا يكون التغيير الذي يتم إجراؤه في جزء واحد من البرنامج مرئيًا في كل مكان. يتم إنشاء static let بشكل كسول عند أول استخدام، وبأمان حتى عندما تبدأ عدة خيوط في الوقت نفسه:

final class Counter {
    static let shared = Counter()
    private(set) var hits = 0
    private init() {}
    func hit() { hits += 1 }
}

func pageA() { Counter.shared.hit() }
func pageB() { Counter.shared.hit(); Counter.shared.hit() }
pageA()
pageB()
print(Counter.shared.hits)
print(Counter.shared === Counter.shared)

الناتج:

3
true

نظرًا لأن المُهيئ خاص، فإن Settings() لا يُترجم خارج الصنف، مما يحافظ على وعد «واحد فقط»:

// let other = Settings()   // error: مُهيئ 'Settings' غير قابل للوصول بسبب مستوى الحماية 'private'

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

// أوضح للاختبار: يتم تمرير الـ logger
func process(order: Order, log: Logger) { log.write("processing \(order.id)") }
challenge icon

التحدي

سهل

أكمل singleton Logger. يحتوي على مثيل static let shared، ومهيئ خاص، ومصفوفة entries من النوع private(set)، وlog(_:level:) التي تخزّن [INFO] message أو [ERROR] message (المستوى هو info أو error). إن errorCount خاصية محسوبة. يستدعي الكود الم provided Logger.shared من دالتين مختلفتين، لذا يجب أن تصل كلتاهما إلى المثيل نفسه.

يقرأ الكود الم provided الأسطر info,message أو error,message، ويرسل أسطر المعلومات عبر handleInfo وأسطر الأخطاء عبر handleError، ثم يطبع كل إدخال وerrors: 1.

ضع كودك في Logger.swift وFunctions.swift. يحتوي main.swift على كود الإدخال/الإخراج الم provided، ولا يمكن تعديله.

جرّب بنفسك

// كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let p = line.split(separator: ",", maxSplits: 1).map(String.init)
    if p[0] == "error" { handleError(p[1]) } else { handleInfo(p[1]) }
}
for e in Logger.shared.entries { print(e) }
print("errors: \(Logger.shared.errorCount)")
quiz iconاختبر نفسك

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

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

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