نمط Singleton
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 91 من 104.
يضمن نمط Singleton أن يكون للفئة مثيل واحد فقط طوال البرنامج بأكمله، ويوفّر نقطة وصول عامة إليه. ويُعدّ هذا مفيدًا للموارد التي ينبغي مشاركتها، مثل مدير الإعدادات أو المسجّل أو مجموعة اتصالات قاعدة البيانات.
الفكرة الأساسية هي جعل constructor خاصًا بحيث لا يتمكن أي شخص من إنشاء مثيلات مباشرةً، ثم توفير طريقة static تُرجع المثيل الوحيد:
#include <iostream>
#include <string>
class Logger {
private:
std::string logFile;
// مُنشئ خاص - يمنع الإنشاء المباشر
Logger() : logFile("app.log") {}
// حذف مُنشئ النسخ وعامل الإسناد
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// دالة ثابتة للوصول إلى المثيل الوحيد
static Logger& getInstance() {
static Logger instance; // تُنشأ مرة واحدة، وتعيش حتى انتهاء البرنامج
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << "\n";
}
};
int main() {
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");
}إن static Logger instance الموجودة داخل getInstance() هي ميزة في C++11 تُسمى "المتغيرات static السحرية" - وهي آمنة مع الخيوط ومضمونة التهيئة مرة واحدة فقط. ومن خلال حذف مُنشئ النسخ وعامل الإسناد، نمنع أي شخص من إنشاء نسخ من singleton الخاص بنا.
استخدم Singleton باعتدال - فهو يقدّم حالة عامة، مما قد يجعل الاختبار أكثر صعوبة. وهو الأنسب للموارد المشتركة حقًا، حيث قد يتسبب وجود مثيلات متعددة في حدوث مشكلات.
التحدي
سهللنَبْنِ كائنًا منفردًا GameSettings يدير إعدادات تطبيق لعبة. هذه حالة استخدام مثالية لنمط Singleton. تريد مدير إعدادات واحدًا بالضبط يمكن لجميع أجزاء لعبتك الوصول إليه، مما يضمن اتساق الإعدادات في كل مكان.
ستنظّم الكود عبر ثلاثة ملفات:
GameSettings.h: عرّف فئة Singleton التي تدير إعدادات اللعبة.ينبغي لفئة
GameSettingsالخاصة بك تخزين ثلاثة إعدادات كأعضاء private:difficulty(سلسلة نصية)، وvolume(عدد صحيح من 0-100)، وfullscreen(قيمة منطقية). قم بتهيئتها بالقيم الافتراضية:"Normal"و50وfalse.طبّق نمط Singleton بطريقة صحيحة:
- اجعل constructor من النوع private
- احذف copy constructor وassignment operator
- وفّر أسلوبًا static باسم
getInstance()يعيد مرجعًا إلى النسخة الوحيدة
أضف هذه الأساليب العامة للتعامل مع الإعدادات:
setDifficulty(const std::string& diff): يحدّث difficultysetVolume(int vol): يحدّث volumesetFullscreen(bool fs): يحدّث وضع fullscreendisplaySettings(): يطبع جميع الإعدادات الحالية
GameModule.h: أنشئ وحدة منفصلة تصل إلى Singleton.عرّف function باسم
applyGamePresetيأخذ اسم إعداد مسبق (سلسلة نصية). ينبغي لهذه function الوصول إلى Singleton من النوعGameSettingsوتهيئته بناءً على الإعداد المسبق:"casual": يضبط difficulty على"Easy"، وvolume على70، وfullscreen علىfalse"competitive": يضبط difficulty على"Hard"، وvolume على30، وfullscreen علىtrue- أي إعداد مسبق آخر: يُبقي الإعدادات الافتراضية دون تغيير
يوضح هذا كيف يمكن لأجزاء مختلفة من البرنامج الوصول إلى نسخة Singleton نفسها وتعديلها.
main.cpp: اجمع كل شيء معًا لإظهار Singleton أثناء العمل.اقرأ مدخلين:
- اسم إعداد مسبق (سلسلة نصية)
- مستوى volume مخصّص (عدد صحيح)
أولًا، اعرض الإعدادات الافتراضية الأولية عبر استدعاء
displaySettings()على Singleton. اطبع سطرًا فارغًا بعد ذلك.ثم طبّق الإعداد المسبق باستخدام function
applyGamePresetالخاصة بك، واعرض الإعدادات مرة أخرى. اطبع سطرًا فارغًا بعد ذلك.أخيرًا، غيّر volume فقط باستخدام قيمة الإدخال المخصّصة، واعرض الإعدادات النهائية.
ينبغي أن يُخرج الأسلوب displaySettings() بالصيغة الدقيقة التالية:
Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]على سبيل المثال، مع المدخلين casual و85:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Easy
Volume: 70
Fullscreen: no
Difficulty: Easy
Volume: 85
Fullscreen: noمع المدخلين competitive و20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesلاحظ كيف يصل كل من main.cpp وGameModule.h إلى نسخة Singleton نفسها: التغييرات التي تُجرى في مكان واحد تكون مرئية في كل مكان. هذه هي قوة نمط Singleton بالنسبة إلى الموارد المشتركة مثل إعدادات اللعبة.
جرّب بنفسك
#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"
using namespace std;
int main() {
// اقرأ المدخلات
string presetName;
int customVolume;
cin >> presetName;
cin >> customVolume;
// TODO: احصل على مثيل الـ singleton لـ GameSettings
// TODO: اعرض الإعدادات الافتراضية الأولية
// TODO: اطبع سطراً فارغاً
// TODO: طبّق الإعداد المسبق باستخدام الدالة applyGamePreset
// TODO: اعرض الإعدادات بعد تطبيق الإعداد المسبق
// TODO: اطبع سطراً فارغاً
// TODO: تجاوز مستوى الصوت بـ customVolume
// TODO: اعرض الإعدادات النهائية
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت