نمط Singleton
جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 42 من 57.
إن نمط التصميم هو حل مُثبت لمشكلة تصميم تظهر مرارًا وتكرارًا. يضمن نمط singleton أن يكون للصنف نسخة واحدة بالضبط يمكن للبرنامج بأكمله الوصول إليها. في Kotlin، يُعدّ تصريح object مفردًا: يُنشأ مرة واحدة عند أول استخدام، وبأمان حتى عندما تبدأ عدة خيوط في الوقت نفسه:
object Settings {
var theme = "light"
var fontSize = 14
}
fun openEditor() = "editor in ${Settings.theme} theme"داخل main:
Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)الناتج:
editor in dark theme
14يحتفظ النمط Singleton بالحالة التي تشترك فيها أجزاء مختلفة من البرنامج. أبقِ هذه الحالة خاصة وغيّرها فقط من خلال الأساليب، بحيث يمر كل تغيير عبر القواعد نفسها:
object Metrics {
private val hits = mutableMapOf<String, Int>()
fun hit(page: String) { hits[page] = (hits[page] ?: 0) + 1 }
fun report() = hits.entries.joinToString { "${it.key}=${it.value}" }
}
fun home() = Metrics.hit("home")
fun about() = Metrics.hit("about")داخل main:
home(); about(); home()
println(Metrics.report())الناتج:
home=2, about=1عندما تحتاج النسخة الوحيدة إلى بيانات إعداد، يمكن لفئة ذات private constructor إنشاؤها في الكائن المرافق لها عند أول طلب وإرجاع النسخة نفسها بعد ذلك:
class Database private constructor(val url: String) {
companion object {
private var instance: Database? = null
fun connect(url: String): Database =
instance ?: Database(url).also { instance = it }
}
}داخل main:
val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)الناتج:
true
db://mainاستخدم النُسخ المفردة باعتدال. النسخة المفردة هي حالة عامة: يمكن لأي شفرة تغييرها، مما يخفي مَن يعتمد عليها، ولا يمكن للاختبارات أن تبدأ من نسخة جديدة. وهي مناسبة لما يوجد فعليًا مرة واحدة لكل برنامج، مثل الإعدادات أو المُسجِّل. عندما يحتاج جزء واحد فقط من البرنامج إلى كائن، مرّره كمعامل، مع تحديد نوعه كواجهة، حتى يتمكن الاختبار من تمرير كائن وهمي بدلًا منه:
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
class Session(private val clock: Clock) { // التبعية مرئية
val started = clock.now()
}
val session = Session(SystemClock) // اختبار يمرر Clock مزيفالتحدي
سهلأكمل المفردة Logger. لديها minLevel قابل للتغيير (debug أو info أو warn أو error، بهذا الترتيب). تخزّن log(level, message) القيمة [WARN] disk low عندما يكون المستوى على الأقل minLevel، وإلا فإنها تحسب الرسالة ضمن dropped، الذي يمكن للكود خارجها قراءته لكن لا يمكنه تغييره. إن entries قائمة للقراءة فقط بالأسطر المخزّنة، وتُرجع count(level) عدد الأسطر المخزّنة التي لها ذلك المستوى.
دوال المساعدة في Functions.kt متوفرة: إذ تصل إلى Logger نفسها من ملف آخر. يقرأ الكود المتوفر الحد الأدنى للمستوى ثم الأسطر level,message، ويطبع كل إدخال وerrors: 1, dropped: 2.
ضع الكود الخاص بك في Logger.kt وFunctions.kt. يحتوي Main.kt على كود الإدخال/الإخراج المتوفر ولا يمكن تعديله.
جرّب بنفسك
fun main() {
// كود الإدخال/الإخراج المُقدَّم: أبقِه كما هو
val input = generateSequence(::readLine).toList()
Logger.minLevel = input[0]
for (line in input.drop(1)) {
val (level, message) = line.split(",", limit = 2)
when (level) {
"debug" -> debug(message)
"info" -> info(message)
"warn" -> warn(message)
else -> error(message)
}
}
for (e in Logger.entries) println(e)
println("errors: ${Logger.count("error")}, dropped: ${Logger.dropped}")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتالمنشئات الأساسيةكتل init والتحقق من الصحةالمنشئات الثانويةمراجعة - بطاقة المكتبة4الوراثة
الأصناف المفتوحة والأصناف الفرعيةتجاوز الأعضاءsuper والمنشئاتالأصناف المجرّدةمراجعة - دليل الموظفينتدرّب بنفسك: Playground لـ Kotlin