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

نمط Singleton

جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 45 من 64.

يضمن نمط Singleton أن للفئة مثيلاً واحداً فقط ويوفر نقطة وصول عامة إليها. وهذا مفيد لموارد مثل اتصالات قاعدة البيانات أو إعدادات التكوين.

إليك تنفيذ أساسي لنمط 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)  # صحيح
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__ للتحكم في إنشاء الكائنات، مما يضمن وجود مثيل واحد فقط. استخدمه للموارد التي يجب أن تحتوي على نسخة واحدة فقط في جميع أنحاء تطبيقك، مثل اتصالات قاعدة البيانات، أو المسجلات (loggers)، أو مديري التكوين (configuration managers). تذكر أن جميع المتغيرات التي تشير إلى Singleton تشير إلى نفس الكائن في الذاكرة.

challenge icon

التحدي

سهل

في هذا التحدي، ستقوم بتنفيذ نمط التصميم Singleton لفئة اتصال بقاعدة البيانات.

  • قم بتحرير databaseconnection.py لتنفيذ نمط Singleton باتباع تعليقات TODO
  • يحتوي ملف driver.py على سيناريوهات اختبار شاملة ولا ينبغي تعديله

ورقة مرجعية

يضمن نمط Singleton أن الفئة (class) لديها مثيل واحد فقط ويوفر نقطة وصول عالمية إليه.

تنفيذ Singleton أساسي باستخدام __new__:

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 - نفس الكائن

مثال عملي مع اتصال قاعدة البيانات:

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

مثال لمدير الإعدادات (Configuration manager):

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)

استخدم Singleton للموارد التي يجب أن يكون لها نسخة واحدة فقط في جميع أنحاء تطبيقك، مثل اتصالات قاعدة البيانات، أو مسجلات الأحداث (loggers)، أو مديري الإعدادات.

جرّب بنفسك

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اختبر نفسك

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

جميع دروس Object Oriented Programming