תבנית 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 במשורה — הוא מציג מצב גלובלי שעלול להקשות על הבדיקות. הוא מתאים במיוחד למשאבים משותפים באמת, שבהם ריבוי מופעים עלול לגרום לבעיות.
אתגר
קלבואו נבנה מחלקת 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: חברו את כל החלקים יחד כדי להדגים את פעולת המופע היחיד.קראו שתי קלטים:
- שם של קביעה מוגדרת מראש (מחרוזת)
- רמת עוצמת קול מותאמת אישית (מספר שלם)
תחילה, הציגו את הגדרות ברירת המחדל הראשוניות באמצעות קריאה ל-
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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים10סקירה כללית של STL
סקירה כללית והפילוסופיה של STLקונטיינרים של STLאיטרטוריםאלגוריתמים של STLפונקטורים וביטויי למבדהסיכום – תדירות מילים13תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית SingletonFactory ו־Abstract Factoryתבנית Builderתבנית Observerתבנית Strategy2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין