Singleton.
Singleton (النمط الفردي) هو نمط تصميم إنشائي يتيح لك ضمان أن الصنف لديه نسخة واحدة فقط، مع توفير نقطة وصول عالمية لهذه النسخة.
##Intent
Singleton هو نمط تصميم إنشائي يتيح لك ضمان أن الصنف لديه نسخة واحدة فقط، مع توفير نقطة وصول عالمية لهذه النسخة.

##Problem
يحل نمط Singleton مشكلتين في آنٍ واحد، مما ينتهك مبدأ المسؤولية الواحدة:
-
ضمان أن الصنف لديه نسخة واحدة فقط. لماذا يريد أحدهم التحكم في عدد النسخ التي يمتلكها الصنف؟ السبب الأكثر شيوعاً هو التحكم في الوصول إلى مورد مشترك—مثل قاعدة بيانات أو ملف.
إليك كيفية عملها: تخيل أنك أنشأت كائناً، ثم بعد فترة قررت إنشاء كائن جديد. بدلاً من الحصول على كائن جديد، ستحصل على الكائن الذي أنشأته مسبقاً.
لاحظ أن هذا السلوك مستحيل التنفيذ باستخدام مُنشئ عادي، إذ يجب على استدعاء المُنشئ دائماً إرجاع كائن جديد بطبيعة تصميمه.

Clients may not even realize that they’re working with the same object all the time.
-
توفير نقطة وصول عالمية لتلك النسخة. تذكر تلك المتغيرات العالمية التي استخدمتها (حسناً، أنا) لتخزين بعض الكائنات الأساسية؟ رغم أنها مفيدة جداً، إلا أنها غير آمنة للغاية، إذ يمكن لأي كود احتمال الكتابة فوق محتويات تلك المتغيرات وتعطيل التطبيق.
تماماً كالمتغير العالمي، يتيح لك نمط Singleton الوصول إلى كائن ما من أي مكان في البرنامج. غير أنه يحمي تلك النسخة أيضاً من الكتابة فوقها من قِبَل كود آخر.
ثمة جانب آخر لهذه المشكلة: لا تريد أن يتناثر الكود الذي يحل المشكلة رقم 1 في جميع أنحاء برنامجك. من الأفضل بكثير احتواؤه ضمن صنف واحد، خاصةً إذا كان بقية الكود يعتمد عليه.
في الوقت الحاضر، أصبح نمط Singleton شائعاً جداً لدرجة أن الناس قد يطلقون على شيء ما اسم singleton حتى لو حل مشكلة واحدة فقط من المشكلات المدرجة.
##Solution
جميع تطبيقات Singleton لها هاتان الخطوتان المشتركتان:
- جعل المُنشئ الافتراضي خاصاً، لمنع الكائنات الأخرى من استخدام المُشغّل
newمع صنف Singleton. - إنشاء طريقة إنشاء ساكنة تعمل كمُنشئ. تحت الغطاء، تستدعي هذه الطريقة المُنشئ الخاص لإنشاء كائن وتحفظه في حقل ساكن. جميع الاستدعاءات اللاحقة لهذه الطريقة تُرجع الكائن المخزن مؤقتاً.
إذا كان الكود لديك يملك وصولاً إلى صنف Singleton، فإنه يستطيع استدعاء الطريقة الساكنة لـ Singleton. لذا في كل مرة تُستدعى فيها تلك الطريقة، يُرجع دائماً نفس الكائن.
##Structure
-
يُعلن صنف Singleton عن الطريقة الساكنة
getInstanceالتي تُرجع نفس نسخة الصنف.يجب إخفاء مُنشئ Singleton عن كود العميل. يجب أن يكون استدعاء الطريقة
getInstanceهو الطريقة الوحيدة للحصول على كائن Singleton.
##Pseudocode
في هذا المثال، تعمل فئة اتصال قاعدة البيانات كـ Singleton. لا يمتلك هذا الصنف مُنشئاً عاماً، لذا الطريقة الوحيدة للحصول على كائنه هي استدعاء الطريقة getInstance. تُخزّن هذه الطريقة مؤقتاً أول كائن تم إنشاؤه وتُرجعه في جميع الاستدعاءات اللاحقة.
// يُعرّف صنف Database طريقة `getInstance` التي تتيح
// للعملاء الوصول إلى نفس نسخة اتصال قاعدة البيانات
// في جميع أنحاء البرنامج.
class Database is
// يجب الإعلان عن الحقل المخصص لتخزين نسخة Singleton
// كحقل ساكن.
private static field instance: Database
// يجب أن يكون مُنشئ Singleton خاصاً دائماً لمنع
// استدعاءات الإنشاء المباشرة باستخدام المُشغّل `new`.
private constructor Database() is
// بعض كود التهيئة، مثل الاتصال الفعلي
// بخادم قاعدة البيانات.
// ...
// الطريقة الساكنة التي تتحكم في الوصول إلى
// نسخة Singleton.
public static method getInstance() is
if (Database.instance == null) then
acquireThreadLock() and then
// التحقق من أن النسخة لم تتم تهيئتها بعد
// من قِبَل خيط آخر بينما كان هذا الخيط
// ينتظر تحرير القفل.
if (Database.instance == null) then
Database.instance = new Database()
return Database.instance
// أخيراً، يجب أن يُعرّف أي Singleton منطق أعمال
// يمكن تنفيذه على نسخته.
public method query(sql) is
// على سبيل المثال، تمر جميع استعلامات قاعدة البيانات
// لأي تطبيق عبر هذه الطريقة. لذلك يمكنك وضع
// منطق التحكم في معدل الطلبات أو التخزين المؤقت هنا.
// ...
class Application is
method main() is
Database foo = Database.getInstance()
foo.query("SELECT ...")
// ...
Database bar = Database.getInstance()
bar.query("SELECT ...")
// سيحتوي المتغير `bar` على نفس الكائن
// الموجود في المتغير `foo`.
##Applicability
استخدم نمط Singleton عندما يجب أن يمتلك صنف في برنامجك نسخة واحدة فقط متاحة لجميع العملاء؛ على سبيل المثال، كائن قاعدة بيانات واحد مشترك بين أجزاء مختلفة من البرنامج.
يُعطّل نمط Singleton جميع الوسائل الأخرى لإنشاء كائنات من الصنف باستثناء طريقة الإنشاء الخاصة. تقوم هذه الطريقة إما بإنشاء كائن جديد أو إرجاع كائن موجود إذا كان قد أُنشئ مسبقاً.
استخدم نمط Singleton عندما تحتاج إلى تحكم أكثر صرامة في المتغيرات العالمية.
على عكس المتغيرات العالمية، يضمن نمط Singleton وجود نسخة واحدة فقط من الصنف. لا شيء، باستثناء صنف Singleton نفسه، يمكنه استبدال النسخة المخزنة مؤقتاً.
لاحظ أنه يمكنك دائماً تعديل هذا القيد والسماح بإنشاء أي عدد من نسخ Singleton. الكود الوحيد الذي يحتاج إلى تغيير هو جسم الطريقة getInstance.
##How to Implement
-
أضف حقلاً ساكناً خاصاً إلى الصنف لتخزين نسخة Singleton.
-
أعلن طريقة إنشاء ساكنة عامة للحصول على نسخة Singleton.
-
نفّذ "التهيئة الكسولة" داخل الطريقة الساكنة. يجب أن تُنشئ كائناً جديداً في أول استدعاء وتضعه في الحقل الساكن. يجب أن تُرجع الطريقة دائماً تلك النسخة في جميع الاستدعاءات اللاحقة.
-
اجعل مُنشئ الصنف خاصاً. ستظل الطريقة الساكنة للصنف قادرة على استدعاء المُنشئ، لكن ليس الكائنات الأخرى.
-
راجع كود العميل واستبدل جميع الاستدعاءات المباشرة لمُنشئ Singleton باستدعاءات طريقة الإنشاء الساكنة.
##Pros & Cons
- يمكنك التأكد من أن الصنف لديه نسخة واحدة فقط.
- تحصل على نقطة وصول عالمية لتلك النسخة.
- يتم تهيئة كائن Singleton فقط عند طلبه للمرة الأولى.
- ينتهك مبدأ المسؤولية الواحدة. يحل النمط مشكلتين في وقت واحد.
- يمكن أن يُخفي نمط Singleton تصميماً سيئاً، مثلاً عندما تعرف مكونات البرنامج أكثر مما ينبغي عن بعضها.
- يتطلب النمط معالجة خاصة في البيئة متعددة الخيوط حتى لا تُنشئ خيوط متعددة كائن Singleton عدة مرات.
- قد يصعب اختبار كود العميل الخاص بـ Singleton باختبارات الوحدة، إذ تعتمد كثير من أطر الاختبار على الوراثة عند إنتاج الكائنات الوهمية. ولأن مُنشئ صنف Singleton خاص وتجاوز الطرق الساكنة مستحيل في معظم اللغات، ستحتاج إلى إيجاد طريقة إبداعية لمحاكاة Singleton. أو لا تكتب الاختبارات أصلاً. أو لا تستخدم نمط Singleton.
##Relations with Other Patterns
-
يمكن في الغالب تحويل صنف Facade إلى Singleton إذ يكفي في معظم الحالات كائن facade واحد.
-
يشبه Flyweight نمط Singleton إذا تمكنت من اختزال جميع الحالات المشتركة للكائنات في كائن flyweight واحد. غير أن ثمة فرقين أساسيين بين هذين النمطين:
- يجب أن تكون هناك نسخة Singleton واحدة فقط، في حين يمكن أن يمتلك صنف Flyweight نسخاً متعددة بحالات داخلية مختلفة.
- يمكن أن يكون كائن Singleton قابلاً للتغيير. كائنات Flyweight غير قابلة للتغيير.
-
يمكن تطبيق Abstract Factories وBuilders وPrototypes جميعها كـ Singletons.