Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط 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 باعتدال - فهو يقدّم حالة عامة، مما قد يجعل الاختبار أكثر صعوبة. وهو الأنسب للموارد المشتركة حقًا، حيث قد يتسبب وجود مثيلات متعددة في حدوث مشكلات.

challenge icon

التحدي

سهل

لنَبْنِ كائنًا منفردًا 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): يحدّث difficulty
    • setVolume(int vol): يحدّث volume
    • setFullscreen(bool fs): يحدّث وضع fullscreen
    • displaySettings(): يطبع جميع الإعدادات الحالية
  • 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 أثناء العمل.

    اقرأ مدخلين:

    1. اسم إعداد مسبق (سلسلة نصية)
    2. مستوى 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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت