Menu
Coddy logo textTech

תבנית Singleton

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Python של Coddy. שיעור 45 מתוך 64.

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

הנה מימוש בסיסי של סינגלטון:

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

המתודה __new__ שולטת ביצירת אובייקטים. היא בודקת אם מופע כבר קיים לפני יצירת מופע חדש.

צרו שני מופעים של המחלקה Singleton:

singleton1 = Singleton()
singleton2 = Singleton()

בדקו אם שני המשתנים מפנים לאותו אובייקט:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # אותה כתובת זיכרון
print(id(singleton2))            # אותה כתובת זיכרון

הנה דוגמה מעשית יותר עם חיבור למסד נתונים:

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
            print("Creating new database connection")
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

# הגישה הראשונה יוצרת את החיבור
db1 = DatabaseConnection()
print(db1.connection)

# הגישה השנייה משתמשת שוב באותו חיבור
db2 = DatabaseConnection()
print(db2.connection)

print(db1.query("SELECT * FROM users"))
print(db1 is db2)

צרו מנהל תצורה באמצעות Singleton:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

config1 = Config()
config1.set_setting("debug", True)

config2 = Config()
print(config2.get_setting("debug"))  # True - אותן הגדרות

פלט:

True
140234567890123
140234567890123
Creating new database connection
Connected to MySQL database
Connected to MySQL database
Executing: SELECT * FROM users
True
True

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

challenge icon

אתגר

קל

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

  • ערוך את databaseconnection.py כדי לממש את תבנית Singleton בהתאם להערות TODO
  • הקובץ driver.py מכיל תרחישי בדיקה נרחבים ואין לשנות אותו

נסו בעצמכם

from databaseconnection import DatabaseConnection

# מטפל מקיף במקרי בדיקה
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # אמור להדפיס True

elif test_case == "connect_state":
    db = DatabaseConnection()
    print(f"Initial connected state: {db.connected}")  # אמור להדפיס False
    db.connect()
    print(f"After connect: {db.connected}")  # אמור להדפיס True

elif test_case == "disconnect_state":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()
    print(f"After disconnect: {db.connected}")  # אמור להדפיס False

elif test_case == "multiple_instances_same_state":
    db1 = DatabaseConnection()
    db1.connect()
    db2 = DatabaseConnection()
    print(f"db2 connected state: {db2.connected}")  # אמור להדפיס True

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # אמור להדפיס "localhost"
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # אמור להדפיס "new-server"

elif test_case == "init_once":
    db1 = DatabaseConnection()
    db1.host = "custom-host"
    db2 = DatabaseConnection()
    print(f"Both instances have same host: {db1.host == db2.host}")  # אמור להדפיס True
    print(f"Host value: {db1.host}")  # אמור להדפיס "custom-host"

elif test_case == "connect_message":
    db = DatabaseConnection()
    db.connect()  # אמור להדפיס "Connected to database at localhost"

elif test_case == "disconnect_message":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()  # אמור להדפיס "Disconnected from database"

elif test_case == "attribute_modification":
    db1 = DatabaseConnection()
    db1.port = 3306
    db2 = DatabaseConnection()
    print(f"db2 has port attribute: {hasattr(db2, 'port')}")  # אמור להדפיס True
    print(f"db2 port value: {db2.port}")  # אמור להדפיס 3306

elif test_case == "reset_connection":
    db1 = DatabaseConnection()
    db1.connect()
    print(f"Connected state: {db1.connected}")  # אמור להדפיס True
    db2 = DatabaseConnection()
    print(f"New instance connected state: {db2.connected}")  # אמור להדפיס True
    db2.disconnect()
    db3 = DatabaseConnection()
    print(f"After disconnect, new instance state: {db3.connected}")  # אמור להדפיס False

elif test_case == "multiple_connects":
    db = DatabaseConnection()
    db.connect()
    db.connect()
    db.connect()
    print(f"Connected state after multiple connects: {db.connected}")  # אמור להדפיס True
    db.disconnect()
    print(f"Connected state after disconnect: {db.connected}")  # אמור להדפיס False

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # עדיין ידפיס "Connected to database at localhost"
    # למתודה connect יש הודעה שהוגדרה מראש ואינה משתמשת במאפיין host
    print("Message still shows localhost: True")
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Python אונליין