<span style='font-size:21pt;line-height:100%'>نمط الورقة الوحيدة
The Singleton Pattern</span>
=====================================
هذا النمط نمط الورقـة الوحيدة أو الـ Singleton (لا أدري إن كانت الترجمـة صحيحـة)، لا يهتـم بكيفية خلق أو صنع الكائنـات بل يهتـم أكثر بأن يجعل هـناك كائن واحد فقط من صنف ما، ولا يهـتم أبداً بكيفية خلق هذا الكائن.
هـناك احتياجات كثيرة لمثل هذا النمط وخاصـة في قواعد البيانات، فحينما تقوم بكتابة قاعـدة بيانات فأنت لن تحتاج سوى لكائن واحد فقط وليس لكائنين أو ثلاثة.
على العـموم ستشير هذه المقالة إلى الكلمـة Singleton بكلمة ورقـة وحيدة ، وبالتالي فسأذكر كائن ورقة وحيدة بدلاً من كائن Singleton .
خلق كائن Singleton بإستخدام الدوال الساكنـة
Creating Singleton Using a Static Method
أفضل طريقـة لتوفير نسخـة واحدة من صنف وحيد هـو بإستخدام الدوال الساكنـة ، فكّر الآن في كيفية منع مستخدم الصنف من إمكانية تخليق عـدة كائنات من الصنف ، الطريقة هي بجعل دالة البناء دالة خاصـة ، وبالتالي فسيستحيل إمكانية صنع أي كائن من الصنف ، وواجبك الآن هـو توفير كائن وحيد فقط من هذا الصنف .
الآن كل ما أريده منك هـو التفكير بهذا الكائن:
1. class single
2. {
3. single();
4. static bool instance_sure;
5. public:
6. static single* GetSingle();
7. };
8.
9. bool single::instance_sure=false;
10.
11. single::single(){}
12.
13. single* single::GetSingle()
14. {
15. if (!instance_flag){instance_sure=true;
16. return new single;}
17. else {return NULL;}
18. }الكائن Single :
تم تعريف هذا الصنف في الأسطر 1-18 .
أنظر إلى دالة البناء في السطر 3 تجـد أنها دالة خاصـة ، وبالتالي فإن هذا السطر يعتبر خاطيء :
single* a= new single();
والسبب لأنه يقوم باستدعاء دالة البناء من خارج الصنف أو الكائن single .
أنظر إلى المتغير الساكن من النـوع bool وهـو instance_sure ، هذا المتغير يتأكد من عـدد الكائنات التي تم إنشاؤها من الصنف singleton . أنظر إلى السطر 9 لقد تمت تهيئة المتغير instance_sure بالقيمة false .
الآن فإن هذا الصنف لا يسمح لك إلا بإنشاء كائن واحد فقط لا غيـر ، أما عـن طريقة إنشاء هذا الكائن الوحيد ، فهي مثل الموجودة في هذا السطر:
single* a= single::GetSingle();
لاحـظ الدالة GetSingle وطريقة تعريفها في الأسطر من 13 إلى 18 ، تجد أن هذه الدالة تعيد ذاكرة أو مرجعيـة من النـوع single ، وهـي إما أن تكون NULL أو single ، والذي يحدد هذا الشيء هـو المتغير الساكـن instance_sure ، في حال كانت قيمة هذا المتغير تساوي القيمـة false ، فستقوم الدالة GetSingle باختبار هذا المتغير وفي حال كانت قيمتـه false ، فإنها تجعل قيمتـه تساوي true وتقوم بحجز للذاكرة من النـوع single ، في حال أراد المستخدم حجز الذاكرة من جـديد لكائن آخر فستقوم الدالة باختبار المتغير الساكن instance_sure وسيكون في هذه الحالة true وبالتالي فستعيد القيمـة NULL .
الآن يجب عليك التفكير بتطوير ولو صغير لهذا الكائن ، فمن ناحيـة لو قام المستخدم بإلغاء ذاكرة الكائن وإعادة تخصيصها من جـديد ، فإن عليك التفكير في كيفية حل لهذه المشكلـة ، أنظر إلى هذه الأسطر:
1. single* a= new single(); 2. delete a; 3. a=new single();
وعلى العـموم ونظراً لسهـولة حل هذه المشكلة فسأتركها لك كسؤال في آخر شرح هذا النمط التصميمي.
هـناك أيضاً بعض المشاكل أو بشكل أكثر دقـة بعض الإضافات التي يجب عليك إضافتها إلى هذا الصنف ، من ضمن هذه الإضافات ، الاستثناءات ، كيف ستقوم بتنبيه مستخدم الصنف بأنه قام بحجز جـديد للذاكرة ، أنظر إلى التعديلات على الصنف single ليصبح أكثر فعالية ، وحاول أن ترى الإضافات الجـديدة قبل قراءة الأسطر التي تحت الكود:
1. class single
2. {
3. single();
4. static bool instance_flag;
5. public:
6. static single* GetSingle();
7. class Exception
8. {
9. string base;
10. public:
11. Exception():base("ThereIsNoInstance"){}
12. void PrintException(){cout << "ThereIsNoInstance";}
13. };
14. };
15. bool single::instance_flag=false;
16.
17. single::single(){}
18.
19. single* single::GetSingle()
20. {
21. if (!instance_flag){instance_flag=true;
22. return new single;}
23. else {throw Exception();return NULL;}
24. }حتى تفهـم بشكل أوضح أنظر إلى الدالة main والتي سترى فيها كيفية استخدام هذا النمط التصميمي:
1. int main()
2. {
3. try{
4. single* a = single::GetSingle();
5. single* b = single::GetSingle();
6. }
7.
8. catch(single::Exception a)
9. {
10. cout << endl; a.PrintException(); cout << endl;
11. }
12. return 0;
13. }أنظر إلى السطر الرابع من الدالة main ، تجد أن مستخدم الصنف سيقوم بحجز للذاكرة دون أن تحدث أية مشاكل.
أنظر إلى السطر الخامس وحاول تتبع تنفيذ البرنامج ، البرنامج سيتوقف عـن العـمل في هذا السطر.
في هذا السطر الخامس سيقوم المستخدم بحجز آخر للذاكرة ، وبما أن قيمـة المتغير instance_sure ، أصبحت true بفعل تنفيذ السطر الرابع ، فلن ينجح حجز الذاكرة ، سينتقل التنفيذ الآن إلى السطر 19 من الصنف single ، في السطر 21 سيتأكد البرنامج إن كانت قيمـة المتغير instance_sure هي false وبما أنها غير ذلك أي true سينتقل التنفيذ إلى الجملة else في السطر 23 والتي ستقوم بإلقاء أحد الاستثناءات وهـو الصنف Exception ، وبالتالي فإن الكتلة catch في السطر 8 من الدالة main ، ستقوم بالتقاطـه وتقوم بتنفيذ نفسها ، وأول أمر ستقوم بـه هـو استدعاء التابع أو الدالة PrintException وهي دالة عضو للصنف Exception ، هذه الدالة لا تقوم بأي شيء سوى بطباعـة الرسالة ThereIsNoInstance ، ومهمتها هي تنبيه المستخدم لعـدم وجود ذاكرة ، بعـد ذلك سينتهي تنفيذ البرنامج تلقائياً.
قد تختلف معـي في ضرورة إنهاء البرنامج حالما يقوم المستخدم بحجز جـديد للذاكرة ، إن السبب في ذلك هـو أن المستخدم ربما سيستخدم الصنف single الجـديد في مهام كثيرة ومتعـددة ، وكما تعلم فإن هذا الكائن لن يكون له أي وجود في البرنامج لأن قيمتـه NULL أو صفر ، وإنهاء البرنامج بطريقة آمنـة أفضل من أن ينتهي بنفسـه وأن يظل مستخدم الصنف يبحث عـن الخطأ دون أن يجد له أي أثر.
