نمط 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 تشير إلى نفس الكائن في الذاكرة.
التحدي
سهلفي هذا التحدي، ستقوم بتنفيذ نمط التصميم 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")يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsمعامل selfMethodsAttributesدالة البناء (__init__)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةدالة ()superإعادة تعريف الدوال (Method Overriding)الوراثة المتعددةترتيب استدعاء الدوال (Method Resolution Order)مراجعة - هيكلية الموظفين5تعدد الأشكال
مراجعة إعادة تعريف الدوالمفهوم Duck Typingالأصناف والدوال المجردةتصميم الواجهاتملخص - حاسبة الأشكال