Menu
Coddy logo textTech

תבנית Singleton

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 91 מתוך 104.

תבנית Singleton מבטיחה שלמחלקה תהיה רק מופע אחד לאורך כל התוכנית ומספקת נקודת גישה גלובלית אליו. הדבר שימושי עבור משאבים שכדאי לשתף, כמו מנהל תצורה, יומן רישום או מאגר חיבורים למסד נתונים.

הרעיון המרכזי הוא להפוך את הבנאי לפרטי, כך שאף אחד לא יוכל ליצור מופעים ישירות, ואז לספק מתודה סטטית שמחזירה את המופע היחיד:

#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 שנקראת "משתנים סטטיים קסומים" — היא בטוחה לשימוש במקביל ומובטח שתאותחל פעם אחת בלבד. על ידי מחיקת בנאי ההעתקה ואופרטור ההשמה, אנחנו מונעים מכל אחד ליצור עותקים של הסינגלטון שלנו.

השתמשו ב-Singleton במשורה — הוא מציג מצב גלובלי שעלול להקשות על הבדיקות. הוא מתאים במיוחד למשאבים משותפים באמת, שבהם ריבוי מופעים עלול לגרום לבעיות.

challenge icon

אתגר

קל

בואו נבנה מחלקת GameSettings יחידנית שמנהלת את ההגדרות של יישום משחק. זהו מקרה שימוש מושלם בתבנית Singleton. נרצה מנהל הגדרות אחד בלבד, שכל חלקי המשחק יוכלו לגשת אליו, כדי להבטיח הגדרות עקביות בכל חלקי המשחק.

תארגנו את הקוד בשלושה קבצים:

  • GameSettings.h: הגדירו את המחלקה היחידנית שמנהלת את הגדרות המשחק.

    המחלקה GameSettings צריכה לאחסן שלוש הגדרות כחברים פרטיים: difficulty (מחרוזת), volume (מספר שלם בין 0-100), ו-fullscreen (בוליאני). אתחלו אותן בערכי ברירת המחדל: "Normal", 50, ו-false.

    ממשו את תבנית Singleton כראוי:

    • הגדירו את הבנאי כפרטי
    • מחקו את בנאי ההעתקה ואת אופרטור ההשמה
    • ספקו מתודת getInstance() סטטית שמחזירה הפניה למופע היחיד

    הוסיפו את המתודות הציבוריות הבאות כדי לפעול עם ההגדרות:

    • setDifficulty(const std::string& diff): מעדכנת את רמת הקושי
    • setVolume(int vol): מעדכנת את עוצמת הקול
    • setFullscreen(bool fs): מעדכנת את מצב המסך המלא
    • displaySettings(): מדפיסה את כל ההגדרות הנוכחיות
  • GameModule.h: צרו מודול נפרד שניגש למופע היחיד.

    הגדירו פונקציה בשם applyGamePreset שמקבלת שם של קביעה מוגדרת מראש (מחרוזת). הפונקציה צריכה לגשת למופע היחיד של GameSettings ולהגדיר אותו בהתאם לקביעה שנבחרה:

    • "casual": מגדירה את רמת הקושי ל-"Easy", את עוצמת הקול ל-70, ואת מצב המסך המלא ל-false
    • "competitive": מגדירה את רמת הקושי ל-"Hard", את עוצמת הקול ל-30, ואת מצב המסך המלא ל-true
    • כל קביעה אחרת: משאירה את הגדרות ברירת המחדל ללא שינוי

    כך מודגם איך חלקים שונים של תוכנית יכולים לגשת לאותו מופע יחיד ולשנות אותו.

  • main.cpp: חברו את כל החלקים יחד כדי להדגים את פעולת המופע היחיד.

    קראו שתי קלטים:

    1. שם של קביעה מוגדרת מראש (מחרוזת)
    2. רמת עוצמת קול מותאמת אישית (מספר שלם)

    תחילה, הציגו את הגדרות ברירת המחדל הראשוניות באמצעות קריאה ל-displaySettings() במופע היחיד. לאחר מכן הדפיסו שורה ריקה.

    לאחר מכן, החילו את הקביעה המוגדרת מראש באמצעות הפונקציה applyGamePreset והציגו שוב את ההגדרות. לאחר מכן הדפיסו שורה ריקה.

    לבסוף, שנו רק את עוצמת הקול לערך הקלט המותאם אישית והציגו את ההגדרות הסופיות.

המתודה 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 עבור משאבים משותפים כמו הגדרות משחק.

נסו בעצמכם

#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++ אונליין