Singleton — نمط إنشائي يضمن مثيلاً واحداً للفئة ويوفر نقطة وصول عامة إليها. Singleton يُستخدم على نطاق واسع في تطوير التطبيقات المحمولة للموارد المشتركة: عملاء الشبكة، قواعد البيانات، مديري الإعدادات. تم وصف النمط في كتاب GoF الكلاسيكي (1994) ولا يزال واحداً من أكثر الأنماط شهرة. المزيد على Refactoring Guru: Singleton.
النقاط الرئيسية
Singleton — نمط تصميم إنشائي وصفته GoF (Gang of Four) في 1994. يحل النمط مشكلتين: يقيد إنشاء مثيل الفئة بكائن واحد ويوفر وصولاً عاماً إلى هذا الكائن. Singleton مفيد للموارد التي يجب أن تكون فريدة: مصانع الجلسات، مخابئ الصور، مديري اتصال قواعد البيانات، عملاء Crashlytics أو Analytics.
تنفيذ Singleton يتطلب مُنشئاً خاصاً (يمنع الإنشاء الخارجي)، حقلاً ثابتاً بالمثيل الوحيد، وطريقة وصول ثابتة (shared، instance، getInstance). يستدعي العملاء Singleton.shared.method() دون القلق بشأن إنشاء الكائن. النمط شائع في iOS و Android: URLSession.shared، UserDefaults.standard، FirebaseApp.sharedInstance — كلها Singletons. ومع ذلك، الاستخدام المفرط لـ Singleton يؤدي إلى النمط المعاكس Global State.
مشاكل Singleton — تبعيات خفية (الفئات تعتمد ضمنياً على كائن Singleton)، تعقيد الاختبار (لا يمكن استبدال المثيل في الاختبارات دون جهد إضافي)، انتهاك مبدأ المسؤولية الواحدة (Singleton يدير كلاً من مثيله ومنطق الأعمال). تطوير التطبيقات المحمولة الحديث يفضل DI (Dagger، Hilt، Swinject) لإدارة المثيلات الفريدة — حاوية DI تنشئ الكائن مرة واحدة وتحقنه عبر المُنشئ.
Swift Singleton يُنفذ عبر خاصية ثابتة shared مع مُهيئ خاص. منذ Swift 3، التهيئة البطيئة للخصائص الثابتة مضمونة لتكون آمنة للخيوط — يضيف المترجم تلقائياً المزامنة عبر dispatch_once. يكفي تعريف static let shared = Class() وجعل init() خاصاً. Swift لا يتطلب مزامنة إضافية للوصول أحادي الخيط بعد التهيئة.
final class NetworkManager {
// Singleton آمن للخيوط
static let shared = NetworkManager()
private init() {
URLSessionConfiguration.default.timeoutIntervalForRequest = 30
}
private var cache = NSCache<NSString, NSData>()
func fetchData(from url: URL) async throws -> Data {
let key = url.absoluteString as NSString
if let cached = cache.object(forKey: key) {
return cached as Data
}
let (data, _) = try await URLSession.shared.data(from: url)
cache.setObject(data as NSData, forKey: key)
return data
}
}
// الاستخدام
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — العديد من كائنات iOS SDK تستخدم Singleton: UIApplication.shared، UIScreen.main، FileManager.default، NotificationCenter.default، UserDefaults.standard. تستخدم Apple Singleton للخدمات الفريدة فيزيائياً (شاشة واحدة، تطبيق واحد). ينسخ المطورون هذا النمط لخدماتهم الخاصة. في SwiftUI، يتم استبدال الوصول العام إلى Singleton بـ Environment و @EnvironmentObject، مما يحسن قابلية الاختبار.
Kotlin Singleton — الطريقة الأبسط: الكلمة المفتاحية object تعلن فئة مفردة مع تهيئة بطيئة عند أول وصول. Kotlin object آمن للخيوط ولا يتطلب مزامنة إضافية. إذا كان Singleton مع معاملات المُنشئ مطلوباً، يُستخدم companion object مع مفوض lazy. في Android، Singleton غالباً ما يكون ضرورياً لسياق Application والخدمات التي تُهيأ عبر Application.onCreate().
// الخيار 1: object — Singleton بسيط بدون معاملات
object AppPreferences {
private val prefs = Application.instance
.getSharedPreferences("app", Context.MODE_PRIVATE)
var isFirstLaunch: Boolean
get() = prefs.getBoolean("first_launch", true)
set(value) = prefs.edit { putBoolean("first_launch", value) }
}
// الخيار 2: companion object — Singleton مع معاملات
class ApiClient private constructor(baseUrl: String) {
companion object {
@Volatile
private var instance: ApiClient? = null
fun getInstance(baseUrl: String): ApiClient {
return instance ?: this.synchronized {
instance ?: ApiClient(baseUrl).also { instance = it }
}
}
}
fun request(endpoint: String): String { /* ... */ }
}
Singleton في Android SDK — العديد من خدمات النظام في Android تنفذ Singleton: context.getSystemService()، Room.databaseBuilder()، Retrofit.Builder(). تشمل الأمثلة SharedPreferences، MediaPlayer، AudioManager. في تطبيقات Android، يُستخدم Singleton غالباً للمستودعات والمديرين والمصانع. توصي Google باستبدال Singleton بـ DI (Hilt، Koin)، حيث تتم إدارة نطاق Singleton (Scope.Singleton أو @Singleton) بواسطة الحاوية بينما تبقى الفئة قابلة للاختبار.
Thread safety — متطلب أساسي لـ Singleton في البيئات متعددة الخيوط. بدون مزامنة، يمكن لخيطين التحقق في وقت واحد من instance == null وإنشاء مثيلين. الحل هو القفل أثناء الإنشاء الأول والتحرير بعد التهيئة. في Swift، الخصائص الثابتة (static let) آمنة للخيوط افتراضياً. في Kotlin، object آمن للخيوط. لنمط Java في Kotlin، يُستخدم synchronized أو @Volatile + double-check locking.
| اللغة | الآلية | الأمان للخيوط | التهيئة البطيئة |
|---|---|---|---|
| Swift | static let | dispatch_once (تلقائي) | نعم، عند أول وصول |
| Kotlin object | إعلان object | مُهيئ الفئة آمن للخيوط | نعم، عند أول وصول |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | نعم، عبر lazy أو synchronized |
| Java | synchronized + volatile | Double-checked locking | نعم، في getInstance() |
Double-checked locking — نمط للتهيئة البطيئة لـ Singleton. الفحص الأول بدون مزامنة (سريع إذا كان المثيل موجوداً بالفعل)، والثاني داخل synchronized (الإنشاء بواسطة خيط واحد فقط). @Volatile يضمن رؤية التغييرات لجميع الخيوط. بدون volatile، قد يرى خيط آخر كائناً منشأً جزئياً. في Kotlin، مفوض lazy مع LazyThreadSafetyMode.SYNCHRONIZED ينفذ تلقائياً double-checked locking.
Dependency Injection — بديل لـ Singleton لإدارة المثيلات الفردية. حاوية DI (Dagger، Hilt، Koin، Swinject) تنشئ الكائن مرة واحدة في نطاق Singleton وتحقنه عبر المُنشئ. الفئة لا تعرف عن حالة Singleton — الحاوية تقرر. يصبح الكود قابلاً للاختبار: يتم استبدال وحدة DI بوحدة mock في الاختبارات. مزايا DI: تبعيات صريحة في المُنشئ، قابلية التجاوز، دورة حياة موحدة.
متى يكون Singleton مبرراً — كائنات على مستوى النظام: Crashlytics، Analytics، Logging. هذه الخدمات تُهيأ مرة واحدة في AppDelegate/Application وتُستخدم في كل مكان. DI مبالغ فيه لها. Singleton أيضاً مناسب لمخابئ الصور (NSCache، Coil، Glide)، حيث الوصول العام مبرر بالأداء. لكل شيء آخر، DI هو المفضل: يجعل التبعيات مرئية، يبسط الاختبار وإعادة الهيكلة.
النهج المختلط — Singleton مع إمكانية التجاوز للاختبارات. في Swift، بروتوكول + خاصية ثابتة يمكن للاختبارات استبدالها (مثلاً عبر URLProtocol لـ URLSession). في Kotlin، فئة مفتوحة مع خاصية قابلة للحقن، حيث تضع الاختبارات mock عبر الانعكاس أو setter. هذا النهج يحافظ على بساطة Singleton لكنه يوفر إمكانيات الاختبار. توصي Google بـ Hilt لـ Android، Apple لا تفرض DI لـ iOS — الاختيار يعتمد على الفريق.
الأسئلة الشائعة
لا، Singleton هو نمط GoF، لكن استخدامه غير الصحيح المتكرر يحوله إلى النمط المعاكس Global State. Singleton مبرر للموارد الفريدة فيزيائياً (شاشة، طابعة، نظام ملفات). تظهر المشاكل عندما يُستخدم Singleton لإدارة البيانات: تبعيات خفية، تعقيد الاختبار، انتهاك مبدأ المسؤولية الواحدة. البديل الحديث هو DI مع نطاق Singleton.
ثلاثة طرق: (1) عبر بروتوكول — Singleton ينفذ بروتوكولاً، الاختبارات تستبدل التنفيذ؛ (2) عبر DI — Singleton يُحقن كتبعية عبر المُنشئ؛ (3) عبر طريقة reset — Singleton لديه طريقة لإعادة تعيين الحالة في الاختبارات (فقط لبناءات الاختبار). الطريقة الأولى مفضلة، الثالثة خطيرة للإنتاج. Swift يسمح باستبدال الخاصية shared عبر التلاعب في وقت التشغيل في الاختبارات.
Kotlin object هو بناء لغوي ينشئ Singleton على مستوى البايت كود. على عكس تنفيذ Java بمُنشئ خاص و getInstance()، يضمن object الأمان للخيوط، التهيئة البطيئة، ويمنع الوراثة. Java Singleton يتطلب مزامنة يدوية (synchronized) و volatile للعمل بشكل صحيح في البيئات متعددة الخيوط. Kotlin object هو الطريقة الأكثر أماناً وإيجازاً في Android.
توريث Singleton يكسر النمط: إذا كان من الممكن توريث فئة Singleton، يمكن لفئة فرعية إنشاء مثيل ثانٍ، منتهكاً التفرد. في Swift، final class يمنع الوراثة. Kotlin object لا يمكن توريثه (object sealed). إذا كان Singleton مع تنوع مطلوباً، استخدم حاوية DI مع نطاق Singleton: تضمن مثيلاً واحداً وتدعم الوراثة عبر الواجهات.
تُمرر المعاملات عبر init(context: Application) أو getInstance(param). Kotlin object لا يقبل معاملات — استخدم companion object مع طريقة مصنع getInstance(param). Hilt يحل المشكلة: @Singleton + @Inject constructor(context: Application) — حاوية DI تحقن سياق Application تلقائياً. لعميل Retrofit، تُمرر المعاملات (baseUrl، interceptors) عبر builder في وحدة DI.
ملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا